Copybara bot | be50d49 | 2023-11-30 00:16:42 +0100 | [diff] [blame] | 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 | } |