blob: e6b5427d91abeb39ba19141e53bff24c7c024c06 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001<?php
2
3
4namespace lbuchs\WebAuthn\CBOR;
5use lbuchs\WebAuthn\WebAuthnException;
6use 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 */
14class 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}