Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  
   3  
   4  namespace lbuchs\WebAuthn\CBOR;
   5  use lbuchs\WebAuthn\WebAuthnException;
   6  use lbuchs\WebAuthn\Binary\ByteBuffer;
   7  
   8  /**
   9   * Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php
  10   * Copyright © 2018 Thomas Bleeker - MIT licensed
  11   * Modified by Lukas Buchs
  12   * Thanks Thomas for your work!
  13   */
  14  class CborDecoder {
  15      const CBOR_MAJOR_UNSIGNED_INT = 0;
  16      const CBOR_MAJOR_TEXT_STRING = 3;
  17      const CBOR_MAJOR_FLOAT_SIMPLE = 7;
  18      const CBOR_MAJOR_NEGATIVE_INT = 1;
  19      const CBOR_MAJOR_ARRAY = 4;
  20      const CBOR_MAJOR_TAG = 6;
  21      const CBOR_MAJOR_MAP = 5;
  22      const CBOR_MAJOR_BYTE_STRING = 2;
  23  
  24      /**
  25       * @param ByteBuffer|string $bufOrBin
  26       * @return mixed
  27       * @throws WebAuthnException
  28       */
  29      public static function decode($bufOrBin) {
  30          $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
  31  
  32          $offset = 0;
  33          $result = self::_parseItem($buf, $offset);
  34          if ($offset !== $buf->getLength()) {
  35              throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR);
  36          }
  37          return $result;
  38      }
  39  
  40      /**
  41       * @param ByteBuffer|string $bufOrBin
  42       * @param int $startOffset
  43       * @param int|null $endOffset
  44       * @return mixed
  45       */
  46      public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) {
  47          $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
  48  
  49          $offset = $startOffset;
  50          $data = self::_parseItem($buf, $offset);
  51          $endOffset = $offset;
  52          return $data;
  53      }
  54  
  55      // ---------------------
  56      // protected
  57      // ---------------------
  58  
  59      /**
  60       * @param ByteBuffer $buf
  61       * @param int $offset
  62       * @return mixed
  63       */
  64      protected static function _parseItem(ByteBuffer $buf, &$offset) {
  65          $first = $buf->getByteVal($offset++);
  66          $type = $first >> 5;
  67          $val = $first & 0b11111;
  68  
  69          if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) {
  70              return self::_parseFloatSimple($val, $buf, $offset);
  71          }
  72  
  73          $val = self::_parseExtraLength($val, $buf, $offset);
  74  
  75          return self::_parseItemData($type, $val, $buf, $offset);
  76      }
  77  
  78      protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) {
  79          switch ($val) {
  80              case 24:
  81                  $val = $buf->getByteVal($offset);
  82                  $offset++;
  83                  return self::_parseSimple($val);
  84  
  85              case 25:
  86                  $floatValue = $buf->getHalfFloatVal($offset);
  87                  $offset += 2;
  88                  return $floatValue;
  89  
  90              case 26:
  91                  $floatValue = $buf->getFloatVal($offset);
  92                  $offset += 4;
  93                  return $floatValue;
  94  
  95              case 27:
  96                  $floatValue = $buf->getDoubleVal($offset);
  97                  $offset += 8;
  98                  return $floatValue;
  99  
 100              case 28:
 101              case 29:
 102              case 30:
 103                  throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
 104  
 105              case 31:
 106                  throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
 107          }
 108  
 109          return self::_parseSimple($val);
 110      }
 111  
 112      /**
 113       * @param int $val
 114       * @return mixed
 115       * @throws WebAuthnException
 116       */
 117      protected static function _parseSimple($val) {
 118          if ($val === 20) {
 119              return false;
 120          }
 121          if ($val === 21) {
 122              return true;
 123          }
 124          if ($val === 22) {
 125              return null;
 126          }
 127          throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR);
 128      }
 129  
 130      protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) {
 131          switch ($val) {
 132              case 24:
 133                  $val = $buf->getByteVal($offset);
 134                  $offset++;
 135                  break;
 136  
 137              case 25:
 138                  $val = $buf->getUint16Val($offset);
 139                  $offset += 2;
 140                  break;
 141  
 142              case 26:
 143                  $val = $buf->getUint32Val($offset);
 144                  $offset += 4;
 145                  break;
 146  
 147              case 27:
 148                  $val = $buf->getUint64Val($offset);
 149                  $offset += 8;
 150                  break;
 151  
 152              case 28:
 153              case 29:
 154              case 30:
 155                  throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
 156  
 157              case 31:
 158                  throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
 159          }
 160  
 161          return $val;
 162      }
 163  
 164      protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) {
 165          switch ($type) {
 166              case self::CBOR_MAJOR_UNSIGNED_INT: // uint
 167                  return $val;
 168  
 169              case self::CBOR_MAJOR_NEGATIVE_INT:
 170                  return -1 - $val;
 171  
 172              case self::CBOR_MAJOR_BYTE_STRING:
 173                  $data = $buf->getBytes($offset, $val);
 174                  $offset += $val;
 175                  return new ByteBuffer($data); // bytes
 176  
 177              case self::CBOR_MAJOR_TEXT_STRING:
 178                  $data = $buf->getBytes($offset, $val);
 179                  $offset += $val;
 180                  return $data; // UTF-8
 181  
 182              case self::CBOR_MAJOR_ARRAY:
 183                  return self::_parseArray($buf, $offset, $val);
 184  
 185              case self::CBOR_MAJOR_MAP:
 186                  return self::_parseMap($buf, $offset, $val);
 187  
 188              case self::CBOR_MAJOR_TAG:
 189                  return self::_parseItem($buf, $offset); // 1 embedded data item
 190          }
 191  
 192          // This should never be reached
 193          throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR);
 194      }
 195  
 196      protected static function _parseMap(ByteBuffer $buf, &$offset, $count) {
 197          $map = array();
 198  
 199          for ($i = 0; $i < $count; $i++) {
 200              $mapKey = self::_parseItem($buf, $offset);
 201              $mapVal = self::_parseItem($buf, $offset);
 202  
 203              if (!\is_int($mapKey) && !\is_string($mapKey)) {
 204                  throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR);
 205              }
 206  
 207              $map[$mapKey] = $mapVal; // todo dup
 208          }
 209          return $map;
 210      }
 211  
 212      protected static function _parseArray(ByteBuffer $buf, &$offset, $count) {
 213          $arr = array();
 214          for ($i = 0; $i < $count; $i++) {
 215              $arr[] = self::_parseItem($buf, $offset);
 216          }
 217  
 218          return $arr;
 219      }
 220  }