1 | <?php |
||
2 | |||
3 | namespace Ocsp\Asn1\Der; |
||
4 | |||
5 | use DateTimeImmutable; |
||
6 | use DateTimeZone; |
||
7 | use Ocsp\Asn1\Decoder as DecoderInterface; |
||
8 | use Ocsp\Asn1\Element; |
||
9 | use Ocsp\Asn1\Element\BitString; |
||
10 | use Ocsp\Asn1\Element\GeneralizedTime; |
||
11 | use Ocsp\Asn1\Element\Integer; |
||
12 | use Ocsp\Asn1\Element\ObjectIdentifier; |
||
13 | use Ocsp\Asn1\Element\OctetString; |
||
14 | use Ocsp\Asn1\Element\PrintableString; |
||
15 | use Ocsp\Asn1\Element\RawConstructed; |
||
16 | use Ocsp\Asn1\Element\RawPrimitive; |
||
17 | use Ocsp\Asn1\Element\Sequence; |
||
18 | use Ocsp\Asn1\Element\Set; |
||
19 | use Ocsp\Asn1\Tag; |
||
20 | use Ocsp\Asn1\TaggableElement; |
||
21 | use Ocsp\Asn1\UniversalTagID; |
||
22 | use Ocsp\Exception\Asn1DecodingException; |
||
23 | use Ocsp\Service\Math; |
||
24 | |||
25 | /** |
||
26 | * Decoder from DER to ASN.1. |
||
27 | */ |
||
28 | class Decoder implements DecoderInterface |
||
29 | { |
||
30 | /** |
||
31 | * {@inheritdoc} |
||
32 | * |
||
33 | * @see \Ocsp\Asn1\Encoder::getEncodingHandle() |
||
34 | */ |
||
35 | 3 | public function getEncodingHandle() |
|
36 | { |
||
37 | 3 | return 'der'; |
|
38 | } |
||
39 | |||
40 | /** |
||
41 | * {@inheritdoc} |
||
42 | * |
||
43 | * @see \Ocsp\Asn1\Decoder::decodeElement() |
||
44 | */ |
||
45 | 3 | public function decodeElement($bytes) |
|
46 | { |
||
47 | 3 | $offset = 0; |
|
48 | |||
49 | 3 | return $this->decodeElementAt($bytes, $offset); |
|
50 | } |
||
51 | |||
52 | /** |
||
53 | * Decode an element at a specific position in a range of bytes. |
||
54 | * |
||
55 | * @param string $bytes |
||
56 | * @param int $offset |
||
57 | * |
||
58 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
59 | * |
||
60 | * @return \Ocsp\Asn1\Element |
||
61 | */ |
||
62 | 3 | protected function decodeElementAt($bytes, &$offset) |
|
63 | { |
||
64 | 3 | list($typeID, $class, $isConstructed) = $this->decodeType($bytes, $offset); |
|
65 | 3 | $encodedValue = $this->extractEncodedValue($bytes, $offset); |
|
66 | |||
67 | 3 | return $isConstructed ? $this->decodeConstructed($typeID, $class, $encodedValue) : $this->decodePrimitive(/** @scrutinizer ignore-type */ $typeID, $class, $encodedValue); |
|
68 | } |
||
69 | |||
70 | /** |
||
71 | * Decode a CONSTRUCTED ASN.1 element. |
||
72 | * |
||
73 | * @param int|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger $typeID |
||
0 ignored issues
–
show
|
|||
74 | * @param string $class |
||
75 | * @param string $encodedValue |
||
76 | * |
||
77 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
78 | * |
||
79 | * @return \Ocsp\Asn1\Element |
||
80 | */ |
||
81 | 3 | protected function decodeConstructed($typeID, $class, $encodedValue) |
|
82 | { |
||
83 | 3 | $offset = 0; |
|
84 | 3 | $encodedValueLength = strlen($encodedValue); |
|
85 | 3 | $elements = []; |
|
86 | 3 | while ($offset < $encodedValueLength) { |
|
87 | 3 | if ($encodedValue[$offset] === "\x00" && isset($encodedValue[$offset + 1]) && $encodedValue[$offset + 1] === "\x00") { |
|
88 | // end of elements in case the length is in indefinite form |
||
89 | break; |
||
90 | } |
||
91 | 3 | $elements[] = $this->decodeElementAt($encodedValue, $offset); |
|
92 | } |
||
93 | 3 | if (count($elements) === 1 && $class !== Element::CLASS_UNIVERSAL && $elements[0] instanceof TaggableElement) { |
|
94 | 3 | return $elements[0]->setTag(Tag::explicit($typeID, $class)); |
|
95 | } |
||
96 | 3 | if (is_int($typeID) && $class === Element::CLASS_UNIVERSAL) { |
|
97 | switch ($typeID) { |
||
98 | 3 | case UniversalTagID::SEQUENCE: |
|
99 | 3 | return Sequence::create($elements); |
|
100 | 3 | case UniversalTagID::SET: |
|
101 | 3 | return Set::create($elements); |
|
102 | } |
||
103 | } |
||
104 | |||
105 | return RawConstructed::create($this->getEncodingHandle(), $typeID, $class, $elements); |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Decode a PRIMITIVE ASN.1 element. |
||
110 | * |
||
111 | * @param int $typeID |
||
112 | * @param string $class |
||
113 | * @param string $encodedValue |
||
114 | * |
||
115 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
116 | * |
||
117 | * @return \Ocsp\Asn1\Element |
||
118 | */ |
||
119 | 3 | protected function decodePrimitive($typeID, $class, $encodedValue) |
|
120 | { |
||
121 | 3 | if ($class === Element::CLASS_UNIVERSAL) { |
|
122 | switch ($typeID) { |
||
123 | 3 | case UniversalTagID::INTEGER: |
|
124 | 3 | return Integer::create($this->decodeInteger($encodedValue)); |
|
125 | 3 | case UniversalTagID::BIT_STRING: |
|
126 | 3 | list($bytes, $numTrailingBits) = $this->decodeBitString($encodedValue); |
|
127 | |||
128 | 3 | return BitString::create($bytes, $numTrailingBits); |
|
129 | 3 | case UniversalTagID::OCTET_STRING: |
|
130 | 3 | return OctetString::create($this->decodeOctetString($encodedValue)); |
|
131 | 3 | case UniversalTagID::OBJECT_IDENTIFIER: |
|
132 | 3 | return ObjectIdentifier::create($this->decodeObjectIdentifier($encodedValue)); |
|
133 | 3 | case UniversalTagID::PRINTABLESTRING: |
|
134 | 3 | return PrintableString::create($this->decodePrintableString($encodedValue)); |
|
135 | 3 | case UniversalTagID::GENERALIZEDTIME: |
|
136 | 2 | return GeneralizedTime::create($this->decodeGeneralizedTime($encodedValue)); |
|
137 | } |
||
138 | } |
||
139 | |||
140 | 3 | return RawPrimitive::create($this->getEncodingHandle(), $typeID, $class, $encodedValue); |
|
141 | } |
||
142 | |||
143 | /** |
||
144 | * Extract the details about at a specific position in a range of bytes. |
||
145 | * |
||
146 | * @param string $bytes |
||
147 | * @param int $offset |
||
148 | * |
||
149 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
150 | * |
||
151 | * @return array The first element is the type ID (int|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger), the second is the class (string), the third is true (if the type is constructed) or false (not constructed) |
||
152 | */ |
||
153 | 3 | protected function decodeType($bytes, &$offset) |
|
154 | { |
||
155 | 3 | if (!isset($bytes[$offset])) { |
|
156 | throw Asn1DecodingException::create(); |
||
157 | } |
||
158 | 3 | $byte = ord($bytes[$offset++]); |
|
159 | 3 | $isConstructed = ($byte & 0b100000) !== 0; |
|
160 | 3 | if (($byte & 0b11000000) === 0b11000000) { |
|
161 | $class = Element::CLASS_PRIVATE; |
||
162 | 3 | } elseif ($byte & 0b10000000) { |
|
163 | 3 | $class = Element::CLASS_CONTEXTSPECIFIC; |
|
164 | 3 | } elseif ($byte & 0b01000000) { |
|
165 | $class = Element::CLASS_APPLICATION; |
||
166 | } else { |
||
167 | 3 | $class = Element::CLASS_UNIVERSAL; |
|
168 | } |
||
169 | 3 | $typeID = $byte & 0b00011111; |
|
170 | 3 | if ($typeID === 0b00011111) { |
|
171 | $typeParts = []; |
||
172 | do { |
||
173 | if (!isset($bytes[$offset])) { |
||
174 | throw Asn1DecodingException::create(); |
||
175 | } |
||
176 | $byte = ord($bytes[$offset++]); |
||
177 | $typeParts[] = $byte & 0b01111111; |
||
178 | } while (($byte & 0b10000000) === 0); |
||
179 | $numTypeParts = count($typeParts); |
||
180 | if ($numTypeParts > PHP_INT_SIZE || ($numTypeParts === PHP_INT_SIZE && $typeParts[$numTypeParts - 1] & 0b10000000)) { |
||
181 | $typeIDBits = ''; |
||
182 | for ($i = 0; $i < $numTypeParts; $i++) { |
||
183 | $typeIDBits .= str_pad(decbin($typeParts[$i]), 7, '0', STR_PAD_LEFT); |
||
184 | } |
||
185 | $typeID = Math::createBigInteger($typeIDBits, 2); |
||
186 | } else { |
||
187 | $typeID = 0; |
||
188 | for ($i = $numTypeParts - 1; $i >= 0; $i--) { |
||
189 | $typeID = ($typeID << 7) + $typeParts[$i]; |
||
190 | } |
||
191 | } |
||
192 | } |
||
193 | |||
194 | 3 | return [$typeID, $class, $isConstructed]; |
|
195 | } |
||
196 | |||
197 | /** |
||
198 | * Extract the bytes representing the value of an element. |
||
199 | * |
||
200 | * @param string $bytes |
||
201 | * @param int $offset |
||
202 | * |
||
203 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
204 | * |
||
205 | * @return string |
||
206 | */ |
||
207 | 3 | protected function extractEncodedValue($bytes, &$offset) |
|
208 | { |
||
209 | 3 | $length = $this->decodeLength($bytes, $offset); |
|
210 | 3 | if ($length === 0) { |
|
211 | 3 | return ''; |
|
212 | } |
||
213 | 3 | if ($offset + $length > strlen($bytes)) { |
|
214 | throw Asn1DecodingException::create(); |
||
215 | } |
||
216 | 3 | $encodedValue = substr($bytes, $offset, $length); |
|
217 | 3 | $offset += $length; |
|
218 | |||
219 | 3 | return $encodedValue; |
|
220 | } |
||
221 | |||
222 | /** |
||
223 | * Extract the length (in bytes) of the encoded value an element. |
||
224 | * |
||
225 | * @param string $bytes |
||
226 | * @param int $offset |
||
227 | * |
||
228 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
229 | * |
||
230 | * @return int |
||
231 | */ |
||
232 | 3 | protected function decodeLength($bytes, &$offset) |
|
233 | { |
||
234 | 3 | if (!isset($bytes[$offset])) { |
|
235 | throw Asn1DecodingException::create(); |
||
236 | } |
||
237 | 3 | $byte = ord($bytes[$offset++]); |
|
238 | 3 | if (($byte & 0b10000000) === 0) { |
|
239 | // short form |
||
240 | 3 | return $byte; |
|
241 | } |
||
242 | 3 | if ($byte === 0b10000000) { |
|
243 | // indefinite form |
||
244 | return strlen($bytes) - $offset; |
||
245 | } |
||
246 | // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only |
||
247 | // support it up to four. |
||
248 | 3 | $numLenghtBytes = $byte & 0b01111111; |
|
249 | 3 | if ($numLenghtBytes === 0) { |
|
250 | throw Asn1DecodingException::create(); |
||
251 | } |
||
252 | 3 | $length = 0; |
|
253 | 3 | for ($i = 0; $i < $numLenghtBytes; $i++) { |
|
254 | 3 | if (!isset($bytes[$offset])) { |
|
255 | throw Asn1DecodingException::create(); |
||
256 | } |
||
257 | 3 | $byte = ord($bytes[$offset++]); |
|
258 | 3 | if ($i === PHP_INT_SIZE || ($i === PHP_INT_SIZE - 1 && $byte & 0b10000000)) { |
|
259 | throw Asn1DecodingException::create('Element length too long for this implementation'); |
||
260 | } |
||
261 | 3 | $length = ($length << 8) + $byte; |
|
262 | } |
||
263 | |||
264 | 3 | return $length; |
|
265 | } |
||
266 | |||
267 | /** |
||
268 | * Decode the value of an INTEGER element. |
||
269 | * |
||
270 | * @param string $bytes |
||
271 | * |
||
272 | * @return int|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger |
||
273 | */ |
||
274 | 3 | protected function decodeInteger($bytes) |
|
275 | { |
||
276 | 3 | $numBytes = strlen($bytes); |
|
277 | 3 | $firstByte = ord($bytes[0]); |
|
278 | 3 | $isNegative = ($firstByte & 0b10000000) !== 0; |
|
279 | 3 | if ($isNegative === false) { |
|
280 | switch ($numBytes) { |
||
281 | 3 | case 1: |
|
282 | 3 | return $firstByte; |
|
283 | 3 | case 2: |
|
284 | return current(unpack('n', $bytes)); |
||
285 | 3 | case 3: |
|
286 | return current(unpack('N', "\x00" . $bytes)); |
||
287 | 3 | case 4: |
|
288 | return current(unpack('N', $bytes)); |
||
289 | } |
||
290 | 3 | if ($numBytes <= 8 && PHP_INT_SIZE >= 8 && PHP_VERSION_ID >= 50603) { |
|
291 | return current(unpack('J', str_pad($bytes, 8, "\x00", STR_PAD_LEFT))); |
||
292 | } |
||
293 | } |
||
294 | |||
295 | 3 | return Math::createBigInteger($bytes, -256); |
|
296 | } |
||
297 | |||
298 | /** |
||
299 | * Decode the value of a BIT STRING element. |
||
300 | * |
||
301 | * @param string $bytes |
||
302 | * |
||
303 | * @return array The first element contains the bytes (a string), the second element the number of trailing bits (an integer) |
||
304 | */ |
||
305 | 3 | protected function decodeBitString($bytes) |
|
306 | { |
||
307 | 3 | $numTrailingBits = ord($bytes[0]) & 0b01111111; |
|
308 | 3 | $bytes = substr($bytes, 1); |
|
309 | 3 | if ($bytes === false) { |
|
310 | $bytes = ''; |
||
311 | } |
||
312 | |||
313 | 3 | return [$bytes, $numTrailingBits]; |
|
314 | } |
||
315 | |||
316 | /** |
||
317 | * Decode the value of a OCTET STRING element. |
||
318 | * |
||
319 | * @param string $bytes |
||
320 | * |
||
321 | * @return string |
||
322 | */ |
||
323 | 3 | protected function decodeOctetString($bytes) |
|
324 | { |
||
325 | 3 | return $bytes; |
|
326 | } |
||
327 | |||
328 | /** |
||
329 | * Decode the value of a OBJECT IDENTIFIER element. |
||
330 | * |
||
331 | * @param string $bytes |
||
332 | * |
||
333 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
334 | * |
||
335 | * @return string |
||
336 | */ |
||
337 | 3 | protected function decodeObjectIdentifier($bytes) |
|
338 | { |
||
339 | 3 | $byte = ord($bytes[0]); |
|
340 | 3 | $result = sprintf('%d.%d', floor($byte / 40), $byte % 40); |
|
341 | 3 | $len = strlen($bytes); |
|
342 | 3 | $chunkBits = ''; |
|
343 | 3 | $maxIntBits = PHP_INT_SIZE * 8 - 1; |
|
344 | 3 | for ($offset = 1; $offset < $len; $offset++) { |
|
345 | 3 | $byte = ord($bytes[$offset]); |
|
346 | 3 | $chunkBits .= str_pad(decbin($byte & 0b01111111), 7, '0', STR_PAD_LEFT); |
|
347 | 3 | if (($byte & 0b10000000) === 0) { |
|
348 | 3 | $result .= '.'; |
|
349 | 3 | if (strlen($chunkBits) <= $maxIntBits) { |
|
350 | 3 | $result .= (string) bindec($chunkBits); |
|
351 | } else { |
||
352 | $result .= Math::createBigInteger($chunkBits, 2)->toString(); |
||
353 | } |
||
354 | 3 | $chunkBits = ''; |
|
355 | } |
||
356 | } |
||
357 | 3 | if ($chunkBits !== '') { |
|
358 | throw Asn1DecodingException::create(); |
||
359 | } |
||
360 | |||
361 | 3 | return $result; |
|
362 | } |
||
363 | |||
364 | /** |
||
365 | * Decode the value of a PrintableString element. |
||
366 | * |
||
367 | * @param string $bytes |
||
368 | * |
||
369 | * @return string |
||
370 | */ |
||
371 | 3 | protected function decodePrintableString($bytes) |
|
372 | { |
||
373 | 3 | return $bytes; |
|
374 | } |
||
375 | |||
376 | /** |
||
377 | * Decode the value of a GeneralizedTime element. |
||
378 | * |
||
379 | * @param string $bytes |
||
380 | * |
||
381 | * @throws \Ocsp\Exception\Asn1DecodingException |
||
382 | * |
||
383 | * @return \DateTimeImmutable |
||
384 | */ |
||
385 | 2 | protected function decodeGeneralizedTime($bytes) |
|
386 | { |
||
387 | 2 | $matches = null; |
|
388 | 2 | if (!preg_match('/(\d{4}\d{2}\d{2}\d{2}\d{2}\d{2})(?:\.(\d*))?Z$/', $bytes, $matches)) { |
|
389 | throw Asn1DecodingException::create(); |
||
390 | } |
||
391 | 2 | $dateTime = DateTimeImmutable::createFromFormat('!YmdHis.uT', $matches[1] . '.' . (isset($matches[2]) ? $matches[2] : '0') . 'UTC', new DateTimeZone('UTC')); |
|
392 | 2 | $result = $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); |
|
393 | |||
394 | 2 | return $result; |
|
395 | } |
||
396 | } |
||
397 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths