• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlocati / ocsp / 3931153084

pending completion
3931153084

push

github

GitHub
Merge pull request #10 from mlocati/phpseclib3

15 of 15 new or added lines in 3 files covered. (100.0%)

452 of 677 relevant lines covered (66.77%)

1.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.18
/src/Asn1/Der/Decoder.php
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
    public function getEncodingHandle()
36
    {
37
        return 'der';
3✔
38
    }
39

40
    /**
41
     * {@inheritdoc}
42
     *
43
     * @see \Ocsp\Asn1\Decoder::decodeElement()
44
     */
45
    public function decodeElement($bytes)
46
    {
47
        $offset = 0;
3✔
48

49
        return $this->decodeElementAt($bytes, $offset);
3✔
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
    protected function decodeElementAt($bytes, &$offset)
63
    {
64
        list($typeID, $class, $isConstructed) = $this->decodeType($bytes, $offset);
3✔
65
        $encodedValue = $this->extractEncodedValue($bytes, $offset);
3✔
66

67
        return $isConstructed ? $this->decodeConstructed($typeID, $class, $encodedValue) : $this->decodePrimitive($typeID, $class, $encodedValue);
3✔
68
    }
69

70
    /**
71
     * Decode a CONSTRUCTED ASN.1 element.
72
     *
73
     * @param int|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger $typeID
74
     * @param string $class
75
     * @param string $encodedValue
76
     *
77
     * @throws \Ocsp\Exception\Asn1DecodingException
78
     *
79
     * @return \Ocsp\Asn1\Element
80
     */
81
    protected function decodeConstructed($typeID, $class, $encodedValue)
82
    {
83
        $offset = 0;
3✔
84
        $encodedValueLength = strlen($encodedValue);
3✔
85
        $elements = [];
3✔
86
        while ($offset < $encodedValueLength) {
3✔
87
            if ($encodedValue[$offset] === "\x00" && isset($encodedValue[$offset + 1]) && $encodedValue[$offset + 1] === "\x00") {
3✔
88
                // end of elements in case the length is in indefinite form
89
                break;
×
90
            }
91
            $elements[] = $this->decodeElementAt($encodedValue, $offset);
3✔
92
        }
93
        if (count($elements) === 1 && $class !== Element::CLASS_UNIVERSAL && $elements[0] instanceof TaggableElement) {
3✔
94
            return $elements[0]->setTag(Tag::explicit($typeID, $class));
3✔
95
        }
96
        if (is_int($typeID) && $class === Element::CLASS_UNIVERSAL) {
3✔
97
            switch ($typeID) {
98
                case UniversalTagID::SEQUENCE:
3✔
99
                    return Sequence::create($elements);
3✔
100
                case UniversalTagID::SET:
3✔
101
                    return Set::create($elements);
3✔
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
    protected function decodePrimitive($typeID, $class, $encodedValue)
120
    {
121
        if ($class === Element::CLASS_UNIVERSAL) {
3✔
122
            switch ($typeID) {
123
                case UniversalTagID::INTEGER:
3✔
124
                    return Integer::create($this->decodeInteger($encodedValue));
3✔
125
                case UniversalTagID::BIT_STRING:
3✔
126
                    list($bytes, $numTrailingBits) = $this->decodeBitString($encodedValue);
3✔
127

128
                    return BitString::create($bytes, $numTrailingBits);
3✔
129
                case UniversalTagID::OCTET_STRING:
3✔
130
                    return OctetString::create($this->decodeOctetString($encodedValue));
3✔
131
                case UniversalTagID::OBJECT_IDENTIFIER:
3✔
132
                    return ObjectIdentifier::create($this->decodeObjectIdentifier($encodedValue));
3✔
133
                case UniversalTagID::PRINTABLESTRING:
3✔
134
                    return PrintableString::create($this->decodePrintableString($encodedValue));
3✔
135
                case UniversalTagID::GENERALIZEDTIME:
3✔
136
                    return GeneralizedTime::create($this->decodeGeneralizedTime($encodedValue));
2✔
137
            }
138
        }
139

140
        return RawPrimitive::create($this->getEncodingHandle(), $typeID, $class, $encodedValue);
3✔
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<int|\phpseclib\Math\BigInteger|\phpseclib3\Math\BigInteger, string, bool>
152
     */
153
    protected function decodeType($bytes, &$offset)
154
    {
155
        if (!isset($bytes[$offset])) {
3✔
156
            throw Asn1DecodingException::create();
×
157
        }
158
        $byte = ord($bytes[$offset++]);
3✔
159
        $isConstructed = ($byte & 0b100000) !== 0;
3✔
160
        if (($byte & 0b11000000) === 0b11000000) {
3✔
161
            $class = Element::CLASS_PRIVATE;
×
162
        } elseif ($byte & 0b10000000) {
3✔
163
            $class = Element::CLASS_CONTEXTSPECIFIC;
3✔
164
        } elseif ($byte & 0b01000000) {
3✔
165
            $class = Element::CLASS_APPLICATION;
×
166
        } else {
167
            $class = Element::CLASS_UNIVERSAL;
3✔
168
        }
169
        $typeID = $byte & 0b00011111;
3✔
170
        if ($typeID === 0b00011111) {
3✔
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
        return [$typeID, $class, $isConstructed];
3✔
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
    protected function extractEncodedValue($bytes, &$offset)
208
    {
209
        $length = $this->decodeLength($bytes, $offset);
3✔
210
        if ($length === 0) {
3✔
211
            return '';
3✔
212
        }
213
        if ($offset + $length > strlen($bytes)) {
3✔
214
            throw Asn1DecodingException::create();
×
215
        }
216
        $encodedValue = substr($bytes, $offset, $length);
3✔
217
        $offset += $length;
3✔
218

219
        return $encodedValue;
3✔
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
    protected function decodeLength($bytes, &$offset)
233
    {
234
        if (!isset($bytes[$offset])) {
3✔
235
            throw Asn1DecodingException::create();
×
236
        }
237
        $byte = ord($bytes[$offset++]);
3✔
238
        if (($byte & 0b10000000) === 0) {
3✔
239
            // short form
240
            return $byte;
3✔
241
        }
242
        if ($byte === 0b10000000) {
3✔
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
        $numLenghtBytes = $byte & 0b01111111;
3✔
249
        if ($numLenghtBytes === 0) {
3✔
250
            throw Asn1DecodingException::create();
×
251
        }
252
        $length = 0;
3✔
253
        for ($i = 0; $i < $numLenghtBytes; $i++) {
3✔
254
            if (!isset($bytes[$offset])) {
3✔
255
                throw Asn1DecodingException::create();
×
256
            }
257
            $byte = ord($bytes[$offset++]);
3✔
258
            if ($i === PHP_INT_SIZE || ($i === PHP_INT_SIZE - 1 && $byte & 0b10000000)) {
3✔
259
                throw Asn1DecodingException::create('Element length too long for this implementation');
×
260
            }
261
            $length = ($length << 8) + $byte;
3✔
262
        }
263

264
        return $length;
3✔
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
    protected function decodeInteger($bytes)
275
    {
276
        $numBytes = strlen($bytes);
3✔
277
        $firstByte = ord($bytes[0]);
3✔
278
        $isNegative = ($firstByte & 0b10000000) !== 0;
3✔
279
        if ($isNegative === false) {
3✔
280
            switch ($numBytes) {
281
                case 1:
3✔
282
                    return $firstByte;
3✔
283
                case 2:
3✔
284
                    return current(unpack('n', $bytes));
×
285
                case 3:
3✔
286
                    return current(unpack('N', "\x00" . $bytes));
×
287
                case 4:
3✔
288
                    return current(unpack('N', $bytes));
×
289
            }
290
            if ($numBytes <= 8 && PHP_INT_SIZE >= 8 && PHP_VERSION_ID >= 50603) {
3✔
291
                return current(unpack('J', str_pad($bytes, 8, "\x00", STR_PAD_LEFT)));
×
292
            }
293
        }
294

295
        return Math::createBigInteger($bytes, -256);
3✔
296
    }
297

298
    /**
299
     * Decode the value of a BIT STRING element.
300
     *
301
     * @param string $bytes
302
     *
303
     * @return string
304
     */
305
    protected function decodeBitString($bytes)
306
    {
307
        $numTrailingBits = ord($bytes[0]) & 0b01111111;
3✔
308
        $bytes = substr($bytes, 1);
3✔
309
        if ($bytes === false) {
3✔
310
            $bytes = '';
×
311
        }
312

313
        return [$bytes, $numTrailingBits];
3✔
314
    }
315

316
    /**
317
     * Decode the value of a OCTET STRING element.
318
     *
319
     * @param string $bytes
320
     *
321
     * @return string
322
     */
323
    protected function decodeOctetString($bytes)
324
    {
325
        return $bytes;
3✔
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
    protected function decodeObjectIdentifier($bytes)
338
    {
339
        $byte = ord($bytes[0]);
3✔
340
        $result = sprintf('%d.%d', floor($byte / 40), $byte % 40);
3✔
341
        $len = strlen($bytes);
3✔
342
        $chunkBits = '';
3✔
343
        $maxIntBits = PHP_INT_SIZE * 8 - 1;
3✔
344
        for ($offset = 1; $offset < $len; $offset++) {
3✔
345
            $byte = ord($bytes[$offset]);
3✔
346
            $chunkBits .= str_pad(decbin($byte & 0b01111111), 7, '0', STR_PAD_LEFT);
3✔
347
            if (($byte & 0b10000000) === 0) {
3✔
348
                $result .= '.';
3✔
349
                if (strlen($chunkBits) <= $maxIntBits) {
3✔
350
                    $result .= (string) bindec($chunkBits);
3✔
351
                } else {
352
                    $result .= Math::createBigInteger($chunkBits, 2)->toString();
×
353
                }
354
                $chunkBits = '';
3✔
355
            }
356
        }
357
        if ($chunkBits !== '') {
3✔
358
            throw Asn1DecodingException::create();
×
359
        }
360

361
        return $result;
3✔
362
    }
363

364
    /**
365
     * Decode the value of a PrintableString element.
366
     *
367
     * @param string $bytes
368
     *
369
     * @return string
370
     */
371
    protected function decodePrintableString($bytes)
372
    {
373
        return $bytes;
3✔
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
    protected function decodeGeneralizedTime($bytes)
386
    {
387
        $matches = null;
2✔
388
        if (!preg_match('/(\d{4}\d{2}\d{2}\d{2}\d{2}\d{2})(?:\.(\d*))?Z$/', $bytes, $matches)) {
2✔
389
            throw Asn1DecodingException::create();
×
390
        }
391
        $dateTime = DateTimeImmutable::createFromFormat('!YmdHis.uT', $matches[1] . '.' . (isset($matches[2]) ? $matches[2] : '0') . 'UTC', new DateTimeZone('UTC'));
2✔
392
        $result = $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get()));
2✔
393

394
        return $result;
2✔
395
    }
396
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc