Subversion Repositories oidplus

Rev

Rev 1384 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 1
<?php
2
 
3
/**
4
 * Pure-PHP ASN.1 Parser
5
 *
6
 * PHP version 5
7
 *
8
 * ASN.1 provides the semantics for data encoded using various schemes.  The most commonly
9
 * utilized scheme is DER or the "Distinguished Encoding Rules".  PEM's are base64 encoded
10
 * DER blobs.
11
 *
12
 * \phpseclib3\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
13
 *
14
 * Uses the 1988 ASN.1 syntax.
15
 *
16
 * @author    Jim Wigginton <terrafrost@php.net>
17
 * @copyright 2012 Jim Wigginton
18
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
19
 * @link      http://phpseclib.sourceforge.net
20
 */
21
 
22
namespace phpseclib3\File;
23
 
24
use phpseclib3\Common\Functions\Strings;
25
use phpseclib3\File\ASN1\Element;
26
use phpseclib3\Math\BigInteger;
27
 
28
/**
29
 * Pure-PHP ASN.1 Parser
30
 *
31
 * @author  Jim Wigginton <terrafrost@php.net>
32
 */
33
abstract class ASN1
34
{
35
    // Tag Classes
36
    // http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
37
    const CLASS_UNIVERSAL        = 0;
38
    const CLASS_APPLICATION      = 1;
39
    const CLASS_CONTEXT_SPECIFIC = 2;
40
    const CLASS_PRIVATE          = 3;
41
 
42
    // Tag Classes
43
    // http://www.obj-sys.com/asn1tutorial/node124.html
44
    const TYPE_BOOLEAN           = 1;
45
    const TYPE_INTEGER           = 2;
46
    const TYPE_BIT_STRING        = 3;
47
    const TYPE_OCTET_STRING      = 4;
48
    const TYPE_NULL              = 5;
49
    const TYPE_OBJECT_IDENTIFIER = 6;
50
    //const TYPE_OBJECT_DESCRIPTOR = 7;
51
    //const TYPE_INSTANCE_OF       = 8; // EXTERNAL
52
    const TYPE_REAL              = 9;
53
    const TYPE_ENUMERATED        = 10;
54
    //const TYPE_EMBEDDED          = 11;
55
    const TYPE_UTF8_STRING       = 12;
56
    //const TYPE_RELATIVE_OID      = 13;
57
    const TYPE_SEQUENCE          = 16; // SEQUENCE OF
58
    const TYPE_SET               = 17; // SET OF
59
 
60
    // More Tag Classes
61
    // http://www.obj-sys.com/asn1tutorial/node10.html
62
    const TYPE_NUMERIC_STRING   = 18;
63
    const TYPE_PRINTABLE_STRING = 19;
64
    const TYPE_TELETEX_STRING   = 20; // T61String
65
    const TYPE_VIDEOTEX_STRING  = 21;
66
    const TYPE_IA5_STRING       = 22;
67
    const TYPE_UTC_TIME         = 23;
68
    const TYPE_GENERALIZED_TIME = 24;
69
    const TYPE_GRAPHIC_STRING   = 25;
70
    const TYPE_VISIBLE_STRING   = 26; // ISO646String
71
    const TYPE_GENERAL_STRING   = 27;
72
    const TYPE_UNIVERSAL_STRING = 28;
73
    //const TYPE_CHARACTER_STRING = 29;
74
    const TYPE_BMP_STRING       = 30;
75
 
76
    // Tag Aliases
77
    // These tags are kinda place holders for other tags.
78
    const TYPE_CHOICE = -1;
79
    const TYPE_ANY    = -2;
80
 
81
    /**
82
     * ASN.1 object identifiers
83
     *
84
     * @var array
85
     * @link http://en.wikipedia.org/wiki/Object_identifier
86
     */
87
    private static $oids = [];
88
 
89
    /**
90
     * ASN.1 object identifier reverse mapping
91
     *
92
     * @var array
93
     */
94
    private static $reverseOIDs = [];
95
 
96
    /**
97
     * Default date format
98
     *
99
     * @var string
100
     * @link http://php.net/class.datetime
101
     */
102
    private static $format = 'D, d M Y H:i:s O';
103
 
104
    /**
105
     * Filters
106
     *
107
     * If the mapping type is self::TYPE_ANY what do we actually encode it as?
108
     *
109
     * @var array
110
     * @see self::encode_der()
111
     */
112
    private static $filters;
113
 
114
    /**
115
     * Current Location of most recent ASN.1 encode process
116
     *
117
     * Useful for debug purposes
118
     *
119
     * @var array
120
     * @see self::encode_der()
121
     */
122
    private static $location;
123
 
124
    /**
125
     * DER Encoded String
126
     *
127
     * In case we need to create ASN1\Element object's..
128
     *
129
     * @var string
130
     * @see self::decodeDER()
131
     */
132
    private static $encoded;
133
 
134
    /**
135
     * Type mapping table for the ANY type.
136
     *
137
     * Structured or unknown types are mapped to a \phpseclib3\File\ASN1\Element.
138
     * Unambiguous types get the direct mapping (int/real/bool).
139
     * Others are mapped as a choice, with an extra indexing level.
140
     *
141
     * @var array
142
     */
143
    const ANY_MAP = [
144
        self::TYPE_BOOLEAN              => true,
145
        self::TYPE_INTEGER              => true,
146
        self::TYPE_BIT_STRING           => 'bitString',
147
        self::TYPE_OCTET_STRING         => 'octetString',
148
        self::TYPE_NULL                 => 'null',
149
        self::TYPE_OBJECT_IDENTIFIER    => 'objectIdentifier',
150
        self::TYPE_REAL                 => true,
151
        self::TYPE_ENUMERATED           => 'enumerated',
152
        self::TYPE_UTF8_STRING          => 'utf8String',
153
        self::TYPE_NUMERIC_STRING       => 'numericString',
154
        self::TYPE_PRINTABLE_STRING     => 'printableString',
155
        self::TYPE_TELETEX_STRING       => 'teletexString',
156
        self::TYPE_VIDEOTEX_STRING      => 'videotexString',
157
        self::TYPE_IA5_STRING           => 'ia5String',
158
        self::TYPE_UTC_TIME             => 'utcTime',
159
        self::TYPE_GENERALIZED_TIME     => 'generalTime',
160
        self::TYPE_GRAPHIC_STRING       => 'graphicString',
161
        self::TYPE_VISIBLE_STRING       => 'visibleString',
162
        self::TYPE_GENERAL_STRING       => 'generalString',
163
        self::TYPE_UNIVERSAL_STRING     => 'universalString',
164
        //self::TYPE_CHARACTER_STRING     => 'characterString',
165
        self::TYPE_BMP_STRING           => 'bmpString'
166
    ];
167
 
168
    /**
169
     * String type to character size mapping table.
170
     *
171
     * Non-convertable types are absent from this table.
172
     * size == 0 indicates variable length encoding.
173
     *
174
     * @var array
175
     */
176
    const STRING_TYPE_SIZE = [
177
        self::TYPE_UTF8_STRING      => 0,
178
        self::TYPE_BMP_STRING       => 2,
179
        self::TYPE_UNIVERSAL_STRING => 4,
180
        self::TYPE_PRINTABLE_STRING => 1,
181
        self::TYPE_TELETEX_STRING   => 1,
182
        self::TYPE_IA5_STRING       => 1,
183
        self::TYPE_VISIBLE_STRING   => 1,
184
    ];
185
 
186
    /**
187
     * Parse BER-encoding
188
     *
189
     * Serves a similar purpose to openssl's asn1parse
190
     *
191
     * @param Element|string $encoded
1042 daniel-mar 192
     * @return ?array
827 daniel-mar 193
     */
194
    public static function decodeBER($encoded)
195
    {
196
        if ($encoded instanceof Element) {
197
            $encoded = $encoded->element;
198
        }
199
 
200
        self::$encoded = $encoded;
201
 
1042 daniel-mar 202
        $decoded = self::decode_ber($encoded);
203
        if ($decoded === false) {
204
            return null;
205
        }
827 daniel-mar 206
 
1114 daniel-mar 207
        return [$decoded];
827 daniel-mar 208
    }
209
 
210
    /**
211
     * Parse BER-encoding (Helper function)
212
     *
213
     * Sometimes we want to get the BER encoding of a particular tag.  $start lets us do that without having to reencode.
214
     * $encoded is passed by reference for the recursive calls done for self::TYPE_BIT_STRING and
215
     * self::TYPE_OCTET_STRING. In those cases, the indefinite length is used.
216
     *
217
     * @param string $encoded
218
     * @param int $start
219
     * @param int $encoded_pos
220
     * @return array|bool
221
     */
222
    private static function decode_ber($encoded, $start = 0, $encoded_pos = 0)
223
    {
224
        $current = ['start' => $start];
225
 
226
        if (!isset($encoded[$encoded_pos])) {
227
            return false;
228
        }
229
        $type = ord($encoded[$encoded_pos++]);
230
        $startOffset = 1;
231
 
232
        $constructed = ($type >> 5) & 1;
233
 
234
        $tag = $type & 0x1F;
235
        if ($tag == 0x1F) {
236
            $tag = 0;
237
            // process septets (since the eighth bit is ignored, it's not an octet)
238
            do {
239
                if (!isset($encoded[$encoded_pos])) {
240
                    return false;
241
                }
242
                $temp = ord($encoded[$encoded_pos++]);
243
                $startOffset++;
244
                $loop = $temp >> 7;
245
                $tag <<= 7;
246
                $temp &= 0x7F;
247
                // "bits 7 to 1 of the first subsequent octet shall not all be zero"
248
                if ($startOffset == 2 && $temp == 0) {
249
                    return false;
250
                }
251
                $tag |= $temp;
252
            } while ($loop);
253
        }
254
 
255
        $start += $startOffset;
256
 
257
        // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
258
        if (!isset($encoded[$encoded_pos])) {
259
            return false;
260
        }
261
        $length = ord($encoded[$encoded_pos++]);
262
        $start++;
263
        if ($length == 0x80) { // indefinite length
264
            // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
265
            //  immediately available." -- paragraph 8.1.3.2.c
266
            $length = strlen($encoded) - $encoded_pos;
267
        } elseif ($length & 0x80) { // definite length, long form
268
            // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
269
            // support it up to four.
270
            $length &= 0x7F;
271
            $temp = substr($encoded, $encoded_pos, $length);
272
            $encoded_pos += $length;
273
            // tags of indefinte length don't really have a header length; this length includes the tag
274
            $current += ['headerlength' => $length + 2];
275
            $start += $length;
276
            extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
277
            /** @var integer $length */
278
        } else {
279
            $current += ['headerlength' => 2];
280
        }
281
 
282
        if ($length > (strlen($encoded) - $encoded_pos)) {
283
            return false;
284
        }
285
 
286
        $content = substr($encoded, $encoded_pos, $length);
287
        $content_pos = 0;
288
 
289
        // at this point $length can be overwritten. it's only accurate for definite length things as is
290
 
291
        /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
292
           built-in types. It defines an application-independent data type that must be distinguishable from all other
293
           data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
294
           have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
295
           a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
296
           alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
297
           data type; the term CONTEXT-SPECIFIC does not appear.
298
 
299
             -- http://www.obj-sys.com/asn1tutorial/node12.html */
300
        $class = ($type >> 6) & 3;
301
        switch ($class) {
302
            case self::CLASS_APPLICATION:
303
            case self::CLASS_PRIVATE:
304
            case self::CLASS_CONTEXT_SPECIFIC:
305
                if (!$constructed) {
306
                    return [
307
                        'type'     => $class,
308
                        'constant' => $tag,
309
                        'content'  => $content,
310
                        'length'   => $length + $start - $current['start']
311
                    ] + $current;
312
                }
313
 
314
                $newcontent = [];
315
                $remainingLength = $length;
316
                while ($remainingLength > 0) {
317
                    $temp = self::decode_ber($content, $start, $content_pos);
318
                    if ($temp === false) {
319
                        break;
320
                    }
321
                    $length = $temp['length'];
322
                    // end-of-content octets - see paragraph 8.1.5
323
                    if (substr($content, $content_pos + $length, 2) == "\0\0") {
324
                        $length += 2;
325
                        $start += $length;
326
                        $newcontent[] = $temp;
327
                        break;
328
                    }
329
                    $start += $length;
330
                    $remainingLength -= $length;
331
                    $newcontent[] = $temp;
332
                    $content_pos += $length;
333
                }
334
 
335
                return [
336
                    'type'     => $class,
337
                    'constant' => $tag,
338
                    // the array encapsulation is for BC with the old format
339
                    'content'  => $newcontent,
340
                    // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
341
                    // the absence of $content['headerlength'] is how we know if something is indefinite or not.
342
                    // technically, it could be defined to be 2 and then another indicator could be used but whatever.
343
                    'length'   => $start - $current['start']
344
                ] + $current;
345
        }
346
 
347
        $current += ['type' => $tag];
348
 
349
        // decode UNIVERSAL tags
350
        switch ($tag) {
351
            case self::TYPE_BOOLEAN:
352
                // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
353
                if ($constructed || strlen($content) != 1) {
354
                    return false;
355
                }
356
                $current['content'] = (bool) ord($content[$content_pos]);
357
                break;
358
            case self::TYPE_INTEGER:
359
            case self::TYPE_ENUMERATED:
360
                if ($constructed) {
361
                    return false;
362
                }
363
                $current['content'] = new BigInteger(substr($content, $content_pos), -256);
364
                break;
365
            case self::TYPE_REAL: // not currently supported
366
                return false;
367
            case self::TYPE_BIT_STRING:
368
                // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
369
                // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
370
                // seven.
371
                if (!$constructed) {
372
                    $current['content'] = substr($content, $content_pos);
373
                } else {
374
                    $temp = self::decode_ber($content, $start, $content_pos);
375
                    if ($temp === false) {
376
                        return false;
377
                    }
378
                    $length -= (strlen($content) - $content_pos);
379
                    $last = count($temp) - 1;
380
                    for ($i = 0; $i < $last; $i++) {
381
                        // all subtags should be bit strings
382
                        if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
383
                            return false;
384
                        }
385
                        $current['content'] .= substr($temp[$i]['content'], 1);
386
                    }
387
                    // all subtags should be bit strings
388
                    if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
389
                        return false;
390
                    }
391
                    $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
392
                }
393
                break;
394
            case self::TYPE_OCTET_STRING:
395
                if (!$constructed) {
396
                    $current['content'] = substr($content, $content_pos);
397
                } else {
398
                    $current['content'] = '';
399
                    $length = 0;
400
                    while (substr($content, $content_pos, 2) != "\0\0") {
401
                        $temp = self::decode_ber($content, $length + $start, $content_pos);
402
                        if ($temp === false) {
403
                            return false;
404
                        }
405
                        $content_pos += $temp['length'];
406
                        // all subtags should be octet strings
407
                        if ($temp['type'] != self::TYPE_OCTET_STRING) {
408
                            return false;
409
                        }
410
                        $current['content'] .= $temp['content'];
411
                        $length += $temp['length'];
412
                    }
413
                    if (substr($content, $content_pos, 2) == "\0\0") {
414
                        $length += 2; // +2 for the EOC
415
                    }
416
                }
417
                break;
418
            case self::TYPE_NULL:
419
                // "The contents octets shall not contain any octets." -- paragraph 8.8.2
420
                if ($constructed || strlen($content)) {
421
                    return false;
422
                }
423
                break;
424
            case self::TYPE_SEQUENCE:
425
            case self::TYPE_SET:
426
                if (!$constructed) {
427
                    return false;
428
                }
429
                $offset = 0;
430
                $current['content'] = [];
431
                $content_len = strlen($content);
432
                while ($content_pos < $content_len) {
433
                    // if indefinite length construction was used and we have an end-of-content string next
434
                    // see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
435
                    if (!isset($current['headerlength']) && substr($content, $content_pos, 2) == "\0\0") {
436
                        $length = $offset + 2; // +2 for the EOC
437
                        break 2;
438
                    }
439
                    $temp = self::decode_ber($content, $start + $offset, $content_pos);
440
                    if ($temp === false) {
441
                        return false;
442
                    }
443
                    $content_pos += $temp['length'];
444
                    $current['content'][] = $temp;
445
                    $offset += $temp['length'];
446
                }
447
                break;
448
            case self::TYPE_OBJECT_IDENTIFIER:
449
                if ($constructed) {
450
                    return false;
451
                }
452
                $current['content'] = self::decodeOID(substr($content, $content_pos));
453
                if ($current['content'] === false) {
454
                    return false;
455
                }
456
                break;
457
            /* Each character string type shall be encoded as if it had been declared:
458
               [UNIVERSAL x] IMPLICIT OCTET STRING
459
 
460
                 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
461
 
462
               Per that, we're not going to do any validation.  If there are any illegal characters in the string,
463
               we don't really care */
464
            case self::TYPE_NUMERIC_STRING:
465
                // 0,1,2,3,4,5,6,7,8,9, and space
466
            case self::TYPE_PRINTABLE_STRING:
467
                // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
468
                // hyphen, full stop, solidus, colon, equal sign, question mark
469
            case self::TYPE_TELETEX_STRING:
470
                // The Teletex character set in CCITT's T61, space, and delete
471
                // see http://en.wikipedia.org/wiki/Teletex#Character_sets
472
            case self::TYPE_VIDEOTEX_STRING:
473
                // The Videotex character set in CCITT's T.100 and T.101, space, and delete
474
            case self::TYPE_VISIBLE_STRING:
475
                // Printing character sets of international ASCII, and space
476
            case self::TYPE_IA5_STRING:
477
                // International Alphabet 5 (International ASCII)
478
            case self::TYPE_GRAPHIC_STRING:
479
                // All registered G sets, and space
480
            case self::TYPE_GENERAL_STRING:
481
                // All registered C and G sets, space and delete
482
            case self::TYPE_UTF8_STRING:
483
                // ????
484
            case self::TYPE_BMP_STRING:
485
                if ($constructed) {
486
                    return false;
487
                }
488
                $current['content'] = substr($content, $content_pos);
489
                break;
490
            case self::TYPE_UTC_TIME:
491
            case self::TYPE_GENERALIZED_TIME:
492
                if ($constructed) {
493
                    return false;
494
                }
495
                $current['content'] = self::decodeTime(substr($content, $content_pos), $tag);
496
                break;
497
            default:
498
                return false;
499
        }
500
 
501
        $start += $length;
502
 
503
        // ie. length is the length of the full TLV encoding - it's not just the length of the value
504
        return $current + ['length' => $start - $current['start']];
505
    }
506
 
507
    /**
508
     * ASN.1 Map
509
     *
510
     * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
511
     *
512
     * "Special" mappings may be applied on a per tag-name basis via $special.
513
     *
514
     * @param array $decoded
515
     * @param array $mapping
516
     * @param array $special
517
     * @return array|bool|Element|string|null
518
     */
1042 daniel-mar 519
    public static function asn1map(array $decoded, $mapping, $special = [])
827 daniel-mar 520
    {
521
        if (isset($mapping['explicit']) && is_array($decoded['content'])) {
522
            $decoded = $decoded['content'][0];
523
        }
524
 
525
        switch (true) {
526
            case $mapping['type'] == self::TYPE_ANY:
527
                $intype = $decoded['type'];
528
                // !isset(self::ANY_MAP[$intype]) produces a fatal error on PHP 5.6
529
                if (isset($decoded['constant']) || !array_key_exists($intype, self::ANY_MAP) || (ord(self::$encoded[$decoded['start']]) & 0x20)) {
530
                    return new Element(substr(self::$encoded, $decoded['start'], $decoded['length']));
531
                }
532
                $inmap = self::ANY_MAP[$intype];
533
                if (is_string($inmap)) {
534
                    return [$inmap => self::asn1map($decoded, ['type' => $intype] + $mapping, $special)];
535
                }
536
                break;
537
            case $mapping['type'] == self::TYPE_CHOICE:
538
                foreach ($mapping['children'] as $key => $option) {
539
                    switch (true) {
540
                        case isset($option['constant']) && $option['constant'] == $decoded['constant']:
541
                        case !isset($option['constant']) && $option['type'] == $decoded['type']:
542
                            $value = self::asn1map($decoded, $option, $special);
543
                            break;
544
                        case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
545
                            $v = self::asn1map($decoded, $option, $special);
546
                            if (isset($v)) {
547
                                $value = $v;
548
                            }
549
                    }
550
                    if (isset($value)) {
551
                        if (isset($special[$key])) {
552
                            $value = $special[$key]($value);
553
                        }
554
                        return [$key => $value];
555
                    }
556
                }
557
                return null;
558
            case isset($mapping['implicit']):
559
            case isset($mapping['explicit']):
560
            case $decoded['type'] == $mapping['type']:
561
                break;
562
            default:
563
                // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
564
                // let it through
565
                switch (true) {
566
                    case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18
567
                    case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30
568
                    case $mapping['type'] < 18:
569
                    case $mapping['type'] > 30:
570
                        return null;
571
                }
572
        }
573
 
574
        if (isset($mapping['implicit'])) {
575
            $decoded['type'] = $mapping['type'];
576
        }
577
 
578
        switch ($decoded['type']) {
579
            case self::TYPE_SEQUENCE:
580
                $map = [];
581
 
582
                // ignore the min and max
583
                if (isset($mapping['min']) && isset($mapping['max'])) {
584
                    $child = $mapping['children'];
585
                    foreach ($decoded['content'] as $content) {
586
                        if (($map[] = self::asn1map($content, $child, $special)) === null) {
587
                            return null;
588
                        }
589
                    }
590
 
591
                    return $map;
592
                }
593
 
594
                $n = count($decoded['content']);
595
                $i = 0;
596
 
597
                foreach ($mapping['children'] as $key => $child) {
598
                    $maymatch = $i < $n; // Match only existing input.
599
                    if ($maymatch) {
600
                        $temp = $decoded['content'][$i];
601
 
602
                        if ($child['type'] != self::TYPE_CHOICE) {
603
                            // Get the mapping and input class & constant.
604
                            $childClass = $tempClass = self::CLASS_UNIVERSAL;
605
                            $constant = null;
606
                            if (isset($temp['constant'])) {
607
                                $tempClass = $temp['type'];
608
                            }
609
                            if (isset($child['class'])) {
610
                                $childClass = $child['class'];
611
                                $constant = $child['cast'];
612
                            } elseif (isset($child['constant'])) {
613
                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
614
                                $constant = $child['constant'];
615
                            }
616
 
617
                            if (isset($constant) && isset($temp['constant'])) {
618
                                // Can only match if constants and class match.
619
                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
620
                            } else {
621
                                // Can only match if no constant expected and type matches or is generic.
622
                                $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false;
623
                            }
624
                        }
625
                    }
626
 
627
                    if ($maymatch) {
628
                        // Attempt submapping.
629
                        $candidate = self::asn1map($temp, $child, $special);
630
                        $maymatch = $candidate !== null;
631
                    }
632
 
633
                    if ($maymatch) {
634
                        // Got the match: use it.
635
                        if (isset($special[$key])) {
636
                            $candidate = $special[$key]($candidate);
637
                        }
638
                        $map[$key] = $candidate;
639
                        $i++;
640
                    } elseif (isset($child['default'])) {
641
                        $map[$key] = $child['default'];
642
                    } elseif (!isset($child['optional'])) {
643
                        return null; // Syntax error.
644
                    }
645
                }
646
 
647
                // Fail mapping if all input items have not been consumed.
648
                return $i < $n ? null : $map;
649
 
650
            // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
651
            case self::TYPE_SET:
652
                $map = [];
653
 
654
                // ignore the min and max
655
                if (isset($mapping['min']) && isset($mapping['max'])) {
656
                    $child = $mapping['children'];
657
                    foreach ($decoded['content'] as $content) {
658
                        if (($map[] = self::asn1map($content, $child, $special)) === null) {
659
                            return null;
660
                        }
661
                    }
662
 
663
                    return $map;
664
                }
665
 
666
                for ($i = 0; $i < count($decoded['content']); $i++) {
667
                    $temp = $decoded['content'][$i];
668
                    $tempClass = self::CLASS_UNIVERSAL;
669
                    if (isset($temp['constant'])) {
670
                        $tempClass = $temp['type'];
671
                    }
672
 
673
                    foreach ($mapping['children'] as $key => $child) {
674
                        if (isset($map[$key])) {
675
                            continue;
676
                        }
677
                        $maymatch = true;
678
                        if ($child['type'] != self::TYPE_CHOICE) {
679
                            $childClass = self::CLASS_UNIVERSAL;
680
                            $constant = null;
681
                            if (isset($child['class'])) {
682
                                $childClass = $child['class'];
683
                                $constant = $child['cast'];
684
                            } elseif (isset($child['constant'])) {
685
                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
686
                                $constant = $child['constant'];
687
                            }
688
 
689
                            if (isset($constant) && isset($temp['constant'])) {
690
                                // Can only match if constants and class match.
691
                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
692
                            } else {
693
                                // Can only match if no constant expected and type matches or is generic.
694
                                $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false;
695
                            }
696
                        }
697
 
698
                        if ($maymatch) {
699
                            // Attempt submapping.
700
                            $candidate = self::asn1map($temp, $child, $special);
701
                            $maymatch = $candidate !== null;
702
                        }
703
 
704
                        if (!$maymatch) {
705
                            break;
706
                        }
707
 
708
                        // Got the match: use it.
709
                        if (isset($special[$key])) {
710
                            $candidate = $special[$key]($candidate);
711
                        }
712
                        $map[$key] = $candidate;
713
                        break;
714
                    }
715
                }
716
 
717
                foreach ($mapping['children'] as $key => $child) {
718
                    if (!isset($map[$key])) {
719
                        if (isset($child['default'])) {
720
                            $map[$key] = $child['default'];
721
                        } elseif (!isset($child['optional'])) {
722
                            return null;
723
                        }
724
                    }
725
                }
726
                return $map;
727
            case self::TYPE_OBJECT_IDENTIFIER:
728
                return isset(self::$oids[$decoded['content']]) ? self::$oids[$decoded['content']] : $decoded['content'];
729
            case self::TYPE_UTC_TIME:
730
            case self::TYPE_GENERALIZED_TIME:
731
                // for explicitly tagged optional stuff
732
                if (is_array($decoded['content'])) {
733
                    $decoded['content'] = $decoded['content'][0]['content'];
734
                }
735
                // for implicitly tagged optional stuff
736
                // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
737
                // in the wild that OpenSSL decodes without issue so we'll support them as well
738
                if (!is_object($decoded['content'])) {
739
                    $decoded['content'] = self::decodeTime($decoded['content'], $decoded['type']);
740
                }
741
                return $decoded['content'] ? $decoded['content']->format(self::$format) : false;
742
            case self::TYPE_BIT_STRING:
743
                if (isset($mapping['mapping'])) {
744
                    $offset = ord($decoded['content'][0]);
745
                    $size = (strlen($decoded['content']) - 1) * 8 - $offset;
746
                    /*
747
                       From X.680-0207.pdf#page=46 (21.7):
748
 
749
                       "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
750
                        arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
751
                        therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
752
 
753
                    */
754
                    $bits = count($mapping['mapping']) == $size ? [] : array_fill(0, count($mapping['mapping']) - $size, false);
755
                    for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
756
                        $current = ord($decoded['content'][$i]);
757
                        for ($j = $offset; $j < 8; $j++) {
758
                            $bits[] = (bool) ($current & (1 << $j));
759
                        }
760
                        $offset = 0;
761
                    }
762
                    $values = [];
763
                    $map = array_reverse($mapping['mapping']);
764
                    foreach ($map as $i => $value) {
765
                        if ($bits[$i]) {
766
                            $values[] = $value;
767
                        }
768
                    }
769
                    return $values;
770
                }
771
                // fall-through
772
            case self::TYPE_OCTET_STRING:
773
                return $decoded['content'];
774
            case self::TYPE_NULL:
775
                return '';
776
            case self::TYPE_BOOLEAN:
777
            case self::TYPE_NUMERIC_STRING:
778
            case self::TYPE_PRINTABLE_STRING:
779
            case self::TYPE_TELETEX_STRING:
780
            case self::TYPE_VIDEOTEX_STRING:
781
            case self::TYPE_IA5_STRING:
782
            case self::TYPE_GRAPHIC_STRING:
783
            case self::TYPE_VISIBLE_STRING:
784
            case self::TYPE_GENERAL_STRING:
785
            case self::TYPE_UNIVERSAL_STRING:
786
            case self::TYPE_UTF8_STRING:
787
            case self::TYPE_BMP_STRING:
788
                return $decoded['content'];
789
            case self::TYPE_INTEGER:
790
            case self::TYPE_ENUMERATED:
791
                $temp = $decoded['content'];
792
                if (isset($mapping['implicit'])) {
793
                    $temp = new BigInteger($decoded['content'], -256);
794
                }
795
                if (isset($mapping['mapping'])) {
796
                    $temp = (int) $temp->toString();
797
                    return isset($mapping['mapping'][$temp]) ?
798
                        $mapping['mapping'][$temp] :
799
                        false;
800
                }
801
                return $temp;
802
        }
803
    }
804
 
805
    /**
806
     * DER-decode the length
807
     *
808
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
809
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
810
     *
811
     * @param string $string
812
     * @return int
813
     */
814
    public static function decodeLength(&$string)
815
    {
816
        $length = ord(Strings::shift($string));
817
        if ($length & 0x80) { // definite length, long form
818
            $length &= 0x7F;
819
            $temp = Strings::shift($string, $length);
820
            list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
821
        }
822
        return $length;
823
    }
824
 
825
    /**
826
     * ASN.1 Encode
827
     *
828
     * DER-encodes an ASN.1 semantic mapping ($mapping).  Some libraries would probably call this function
829
     * an ASN.1 compiler.
830
     *
831
     * "Special" mappings can be applied via $special.
832
     *
833
     * @param Element|string|array $source
834
     * @param array $mapping
835
     * @param array $special
836
     * @return string
837
     */
838
    public static function encodeDER($source, $mapping, $special = [])
839
    {
840
        self::$location = [];
841
        return self::encode_der($source, $mapping, null, $special);
842
    }
843
 
844
    /**
845
     * ASN.1 Encode (Helper function)
846
     *
847
     * @param Element|string|array|null $source
848
     * @param array $mapping
849
     * @param int $idx
850
     * @param array $special
851
     * @return string
852
     */
1042 daniel-mar 853
    private static function encode_der($source, array $mapping, $idx = null, array $special = [])
827 daniel-mar 854
    {
855
        if ($source instanceof Element) {
856
            return $source->element;
857
        }
858
 
859
        // do not encode (implicitly optional) fields with value set to default
860
        if (isset($mapping['default']) && $source === $mapping['default']) {
861
            return '';
862
        }
863
 
864
        if (isset($idx)) {
865
            if (isset($special[$idx])) {
866
                $source = $special[$idx]($source);
867
            }
868
            self::$location[] = $idx;
869
        }
870
 
871
        $tag = $mapping['type'];
872
 
873
        switch ($tag) {
874
            case self::TYPE_SET:    // Children order is not important, thus process in sequence.
875
            case self::TYPE_SEQUENCE:
876
                $tag |= 0x20; // set the constructed bit
877
 
878
                // ignore the min and max
879
                if (isset($mapping['min']) && isset($mapping['max'])) {
880
                    $value = [];
881
                    $child = $mapping['children'];
882
 
883
                    foreach ($source as $content) {
884
                        $temp = self::encode_der($content, $child, null, $special);
885
                        if ($temp === false) {
886
                            return false;
887
                        }
888
                        $value[] = $temp;
889
                    }
890
                    /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
891
                        as octet strings with the shorter components being padded at their trailing end with 0-octets.
892
                        NOTE - The padding octets are for comparison purposes only and do not appear in the encodings."
893
 
894
                       -- sec 11.6 of http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf  */
895
                    if ($mapping['type'] == self::TYPE_SET) {
896
                        sort($value);
897
                    }
898
                    $value = implode('', $value);
899
                    break;
900
                }
901
 
902
                $value = '';
903
                foreach ($mapping['children'] as $key => $child) {
904
                    if (!array_key_exists($key, $source)) {
905
                        if (!isset($child['optional'])) {
906
                            return false;
907
                        }
908
                        continue;
909
                    }
910
 
911
                    $temp = self::encode_der($source[$key], $child, $key, $special);
912
                    if ($temp === false) {
913
                        return false;
914
                    }
915
 
916
                    // An empty child encoding means it has been optimized out.
917
                    // Else we should have at least one tag byte.
918
                    if ($temp === '') {
919
                        continue;
920
                    }
921
 
922
                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
923
                    if (isset($child['constant'])) {
924
                        /*
925
                           From X.680-0207.pdf#page=58 (30.6):
926
 
927
                           "The tagging construction specifies explicit tagging if any of the following holds:
928
                            ...
929
                            c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
930
                            AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
931
                            an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
932
                         */
933
                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
934
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
935
                            $temp = $subtag . self::encodeLength(strlen($temp)) . $temp;
936
                        } else {
937
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
938
                            $temp = $subtag . substr($temp, 1);
939
                        }
940
                    }
941
                    $value .= $temp;
942
                }
943
                break;
944
            case self::TYPE_CHOICE:
945
                $temp = false;
946
 
947
                foreach ($mapping['children'] as $key => $child) {
948
                    if (!isset($source[$key])) {
949
                        continue;
950
                    }
951
 
952
                    $temp = self::encode_der($source[$key], $child, $key, $special);
953
                    if ($temp === false) {
954
                        return false;
955
                    }
956
 
957
                    // An empty child encoding means it has been optimized out.
958
                    // Else we should have at least one tag byte.
959
                    if ($temp === '') {
960
                        continue;
961
                    }
962
 
963
                    $tag = ord($temp[0]);
964
 
965
                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
966
                    if (isset($child['constant'])) {
967
                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
968
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
969
                            $temp = $subtag . self::encodeLength(strlen($temp)) . $temp;
970
                        } else {
971
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
972
                            $temp = $subtag . substr($temp, 1);
973
                        }
974
                    }
975
                }
976
 
977
                if (isset($idx)) {
978
                    array_pop(self::$location);
979
                }
980
 
981
                if ($temp && isset($mapping['cast'])) {
982
                    $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
983
                }
984
 
985
                return $temp;
986
            case self::TYPE_INTEGER:
987
            case self::TYPE_ENUMERATED:
988
                if (!isset($mapping['mapping'])) {
989
                    if (is_numeric($source)) {
990
                        $source = new BigInteger($source);
991
                    }
992
                    $value = $source->toBytes(true);
993
                } else {
994
                    $value = array_search($source, $mapping['mapping']);
995
                    if ($value === false) {
996
                        return false;
997
                    }
998
                    $value = new BigInteger($value);
999
                    $value = $value->toBytes(true);
1000
                }
1001
                if (!strlen($value)) {
1002
                    $value = chr(0);
1003
                }
1004
                break;
1005
            case self::TYPE_UTC_TIME:
1006
            case self::TYPE_GENERALIZED_TIME:
1007
                $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
1008
                $format .= 'mdHis';
1009
                // if $source does _not_ include timezone information within it then assume that the timezone is GMT
1010
                $date = new \DateTime($source, new \DateTimeZone('GMT'));
1011
                // if $source _does_ include timezone information within it then convert the time to GMT
1012
                $date->setTimezone(new \DateTimeZone('GMT'));
1013
                $value = $date->format($format) . 'Z';
1014
                break;
1015
            case self::TYPE_BIT_STRING:
1016
                if (isset($mapping['mapping'])) {
1017
                    $bits = array_fill(0, count($mapping['mapping']), 0);
1018
                    $size = 0;
1019
                    for ($i = 0; $i < count($mapping['mapping']); $i++) {
1020
                        if (in_array($mapping['mapping'][$i], $source)) {
1021
                            $bits[$i] = 1;
1022
                            $size = $i;
1023
                        }
1024
                    }
1025
 
1026
                    if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
1027
                        $size = $mapping['min'] - 1;
1028
                    }
1029
 
1030
                    $offset = 8 - (($size + 1) & 7);
1031
                    $offset = $offset !== 8 ? $offset : 0;
1032
 
1033
                    $value = chr($offset);
1034
 
1035
                    for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
1036
                        unset($bits[$i]);
1037
                    }
1038
 
1039
                    $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
1040
                    $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
1041
                    foreach ($bytes as $byte) {
1042
                        $value .= chr(bindec($byte));
1043
                    }
1044
 
1045
                    break;
1046
                }
1047
                // fall-through
1048
            case self::TYPE_OCTET_STRING:
1049
                /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
1050
                   the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
1051
 
1052
                   -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
1053
                $value = $source;
1054
                break;
1055
            case self::TYPE_OBJECT_IDENTIFIER:
1056
                $value = self::encodeOID($source);
1057
                break;
1058
            case self::TYPE_ANY:
1059
                $loc = self::$location;
1060
                if (isset($idx)) {
1061
                    array_pop(self::$location);
1062
                }
1063
 
1064
                switch (true) {
1065
                    case !isset($source):
1066
                        return self::encode_der(null, ['type' => self::TYPE_NULL] + $mapping, null, $special);
1067
                    case is_int($source):
1068
                    case $source instanceof BigInteger:
1069
                        return self::encode_der($source, ['type' => self::TYPE_INTEGER] + $mapping, null, $special);
1070
                    case is_float($source):
1071
                        return self::encode_der($source, ['type' => self::TYPE_REAL] + $mapping, null, $special);
1072
                    case is_bool($source):
1073
                        return self::encode_der($source, ['type' => self::TYPE_BOOLEAN] + $mapping, null, $special);
1074
                    case is_array($source) && count($source) == 1:
1075
                        $typename = implode('', array_keys($source));
1076
                        $outtype = array_search($typename, self::ANY_MAP, true);
1077
                        if ($outtype !== false) {
1078
                            return self::encode_der($source[$typename], ['type' => $outtype] + $mapping, null, $special);
1079
                        }
1080
                }
1081
 
1082
                $filters = self::$filters;
1083
                foreach ($loc as $part) {
1084
                    if (!isset($filters[$part])) {
1085
                        $filters = false;
1086
                        break;
1087
                    }
1088
                    $filters = $filters[$part];
1089
                }
1090
                if ($filters === false) {
1091
                    throw new \RuntimeException('No filters defined for ' . implode('/', $loc));
1092
                }
1093
                return self::encode_der($source, $filters + $mapping, null, $special);
1094
            case self::TYPE_NULL:
1095
                $value = '';
1096
                break;
1097
            case self::TYPE_NUMERIC_STRING:
1098
            case self::TYPE_TELETEX_STRING:
1099
            case self::TYPE_PRINTABLE_STRING:
1100
            case self::TYPE_UNIVERSAL_STRING:
1101
            case self::TYPE_UTF8_STRING:
1102
            case self::TYPE_BMP_STRING:
1103
            case self::TYPE_IA5_STRING:
1104
            case self::TYPE_VISIBLE_STRING:
1105
            case self::TYPE_VIDEOTEX_STRING:
1106
            case self::TYPE_GRAPHIC_STRING:
1107
            case self::TYPE_GENERAL_STRING:
1108
                $value = $source;
1109
                break;
1110
            case self::TYPE_BOOLEAN:
1111
                $value = $source ? "\xFF" : "\x00";
1112
                break;
1113
            default:
1114
                throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', self::$location));
1115
        }
1116
 
1117
        if (isset($idx)) {
1118
            array_pop(self::$location);
1119
        }
1120
 
1121
        if (isset($mapping['cast'])) {
1122
            if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
1123
                $value = chr($tag) . self::encodeLength(strlen($value)) . $value;
1124
                $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
1125
            } else {
1126
                $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
1127
            }
1128
        }
1129
 
1130
        return chr($tag) . self::encodeLength(strlen($value)) . $value;
1131
    }
1132
 
1133
    /**
1134
     * BER-decode the OID
1135
     *
1136
     * Called by _decode_ber()
1137
     *
1138
     * @param string $content
1139
     * @return string
1140
     */
1141
    public static function decodeOID($content)
1142
    {
1143
        static $eighty;
1144
        if (!$eighty) {
1145
            $eighty = new BigInteger(80);
1146
        }
1147
 
1148
        $oid = [];
1149
        $pos = 0;
1150
        $len = strlen($content);
1466 daniel-mar 1151
        // see https://github.com/openjdk/jdk/blob/2deb318c9f047ec5a4b160d66a4b52f93688ec42/src/java.base/share/classes/sun/security/util/ObjectIdentifier.java#L55
1152
        if ($len > 4096) {
1153
            //throw new \RuntimeException("Object identifier size is limited to 4096 bytes ($len bytes present)");
1154
            return false;
1155
        }
827 daniel-mar 1156
 
1157
        if (ord($content[$len - 1]) & 0x80) {
1158
            return false;
1159
        }
1160
 
1161
        $n = new BigInteger();
1162
        while ($pos < $len) {
1163
            $temp = ord($content[$pos++]);
1164
            $n = $n->bitwise_leftShift(7);
1165
            $n = $n->bitwise_or(new BigInteger($temp & 0x7F));
1166
            if (~$temp & 0x80) {
1167
                $oid[] = $n;
1168
                $n = new BigInteger();
1169
            }
1170
        }
1171
        $part1 = array_shift($oid);
1172
        $first = floor(ord($content[0]) / 40);
1173
        /*
1174
          "This packing of the first two object identifier components recognizes that only three values are allocated from the root
1175
           node, and at most 39 subsequent values from nodes reached by X = 0 and X = 1."
1176
 
1177
          -- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22
1178
        */
1179
        if ($first <= 2) { // ie. 0 <= ord($content[0]) < 120 (0x78)
1180
            array_unshift($oid, ord($content[0]) % 40);
1181
            array_unshift($oid, $first);
1182
        } else {
1183
            array_unshift($oid, $part1->subtract($eighty));
1184
            array_unshift($oid, 2);
1185
        }
1186
 
1187
        return implode('.', $oid);
1188
    }
1189
 
1190
    /**
1191
     * DER-encode the OID
1192
     *
1193
     * Called by _encode_der()
1194
     *
1195
     * @param string $source
1196
     * @return string
1197
     */
1198
    public static function encodeOID($source)
1199
    {
1200
        static $mask, $zero, $forty;
1201
        if (!$mask) {
1202
            $mask = new BigInteger(0x7F);
1203
            $zero = new BigInteger();
1204
            $forty = new BigInteger(40);
1205
        }
1206
 
1207
        if (!preg_match('#(?:\d+\.)+#', $source)) {
1208
            $oid = isset(self::$reverseOIDs[$source]) ? self::$reverseOIDs[$source] : false;
1209
        } else {
1210
            $oid = $source;
1211
        }
1212
        if ($oid === false) {
1213
            throw new \RuntimeException('Invalid OID');
1214
        }
1215
 
1216
        $parts = explode('.', $oid);
1217
        $part1 = array_shift($parts);
1218
        $part2 = array_shift($parts);
1219
 
1220
        $first = new BigInteger($part1);
1221
        $first = $first->multiply($forty);
1222
        $first = $first->add(new BigInteger($part2));
1223
 
1224
        array_unshift($parts, $first->toString());
1225
 
1226
        $value = '';
1227
        foreach ($parts as $part) {
1228
            if (!$part) {
1229
                $temp = "\0";
1230
            } else {
1231
                $temp = '';
1232
                $part = new BigInteger($part);
1233
                while (!$part->equals($zero)) {
1234
                    $submask = $part->bitwise_and($mask);
1235
                    $submask->setPrecision(8);
1236
                    $temp = (chr(0x80) | $submask->toBytes()) . $temp;
1237
                    $part = $part->bitwise_rightShift(7);
1238
                }
1239
                $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
1240
            }
1241
            $value .= $temp;
1242
        }
1243
 
1244
        return $value;
1245
    }
1246
 
1247
    /**
1248
     * BER-decode the time
1249
     *
1250
     * Called by _decode_ber() and in the case of implicit tags asn1map().
1251
     *
1252
     * @param string $content
1253
     * @param int $tag
1254
     * @return \DateTime|false
1255
     */
1256
    private static function decodeTime($content, $tag)
1257
    {
1258
        /* UTCTime:
1259
           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1260
           http://www.obj-sys.com/asn1tutorial/node15.html
1261
 
1262
           GeneralizedTime:
1263
           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1264
           http://www.obj-sys.com/asn1tutorial/node14.html */
1265
 
1266
        $format = 'YmdHis';
1267
 
1268
        if ($tag == self::TYPE_UTC_TIME) {
1269
            // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds
1270
            // element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the
1271
            // browsers parse it phpseclib ought to too
1272
            if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) {
1273
                $content = $matches[1] . '00' . $matches[2];
1274
            }
1275
            $prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
1276
            $content = $prefix . $content;
1277
        } elseif (strpos($content, '.') !== false) {
1278
            $format .= '.u';
1279
        }
1280
 
1281
        if ($content[strlen($content) - 1] == 'Z') {
1282
            $content = substr($content, 0, -1) . '+0000';
1283
        }
1284
 
1285
        if (strpos($content, '-') !== false || strpos($content, '+') !== false) {
1286
            $format .= 'O';
1287
        }
1288
 
1289
        // error supression isn't necessary as of PHP 7.0:
1290
        // http://php.net/manual/en/migration70.other-changes.php
1291
        return @\DateTime::createFromFormat($format, $content);
1292
    }
1293
 
1294
    /**
1295
     * Set the time format
1296
     *
1297
     * Sets the time / date format for asn1map().
1298
     *
1299
     * @param string $format
1300
     */
1301
    public static function setTimeFormat($format)
1302
    {
1303
        self::$format = $format;
1304
    }
1305
 
1306
    /**
1307
     * Load OIDs
1308
     *
1309
     * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1310
     * Previously loaded OIDs are retained.
1311
     *
1312
     * @param array $oids
1313
     */
1042 daniel-mar 1314
    public static function loadOIDs(array $oids)
827 daniel-mar 1315
    {
1316
        self::$reverseOIDs += $oids;
1317
        self::$oids = array_flip(self::$reverseOIDs);
1318
    }
1319
 
1320
    /**
1321
     * Set filters
1322
     *
1323
     * See \phpseclib3\File\X509, etc, for an example.
1324
     * Previously loaded filters are not retained.
1325
     *
1326
     * @param array $filters
1327
     */
1042 daniel-mar 1328
    public static function setFilters(array $filters)
827 daniel-mar 1329
    {
1330
        self::$filters = $filters;
1331
    }
1332
 
1333
    /**
1334
     * String type conversion
1335
     *
1336
     * This is a lazy conversion, dealing only with character size.
1337
     * No real conversion table is used.
1338
     *
1339
     * @param string $in
1340
     * @param int $from
1341
     * @param int $to
1342
     * @return string
1343
     */
1344
    public static function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
1345
    {
1346
        // isset(self::STRING_TYPE_SIZE[$from] returns a fatal error on PHP 5.6
1347
        if (!array_key_exists($from, self::STRING_TYPE_SIZE) || !array_key_exists($to, self::STRING_TYPE_SIZE)) {
1348
            return false;
1349
        }
1350
        $insize = self::STRING_TYPE_SIZE[$from];
1351
        $outsize = self::STRING_TYPE_SIZE[$to];
1352
        $inlength = strlen($in);
1353
        $out = '';
1354
 
1355
        for ($i = 0; $i < $inlength;) {
1356
            if ($inlength - $i < $insize) {
1357
                return false;
1358
            }
1359
 
1360
            // Get an input character as a 32-bit value.
1361
            $c = ord($in[$i++]);
1362
            switch (true) {
1363
                case $insize == 4:
1364
                    $c = ($c << 8) | ord($in[$i++]);
1365
                    $c = ($c << 8) | ord($in[$i++]);
1366
                    // fall-through
1367
                case $insize == 2:
1368
                    $c = ($c << 8) | ord($in[$i++]);
1369
                    // fall-through
1370
                case $insize == 1:
1371
                    break;
1372
                case ($c & 0x80) == 0x00:
1373
                    break;
1374
                case ($c & 0x40) == 0x00:
1375
                    return false;
1376
                default:
1377
                    $bit = 6;
1378
                    do {
1379
                        if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1380
                            return false;
1381
                        }
1382
                        $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1383
                        $bit += 5;
1384
                        $mask = 1 << $bit;
1385
                    } while ($c & $bit);
1386
                    $c &= $mask - 1;
1387
                    break;
1388
            }
1389
 
1390
            // Convert and append the character to output string.
1391
            $v = '';
1392
            switch (true) {
1393
                case $outsize == 4:
1394
                    $v .= chr($c & 0xFF);
1395
                    $c >>= 8;
1396
                    $v .= chr($c & 0xFF);
1397
                    $c >>= 8;
1398
                    // fall-through
1399
                case $outsize == 2:
1400
                    $v .= chr($c & 0xFF);
1401
                    $c >>= 8;
1402
                    // fall-through
1403
                case $outsize == 1:
1404
                    $v .= chr($c & 0xFF);
1405
                    $c >>= 8;
1406
                    if ($c) {
1407
                        return false;
1408
                    }
1409
                    break;
1384 daniel-mar 1410
                case ($c & (PHP_INT_SIZE == 8 ? 0x80000000 : (1 << 31))) != 0:
827 daniel-mar 1411
                    return false;
1412
                case $c >= 0x04000000:
1413
                    $v .= chr(0x80 | ($c & 0x3F));
1414
                    $c = ($c >> 6) | 0x04000000;
1415
                    // fall-through
1416
                case $c >= 0x00200000:
1417
                    $v .= chr(0x80 | ($c & 0x3F));
1418
                    $c = ($c >> 6) | 0x00200000;
1419
                    // fall-through
1420
                case $c >= 0x00010000:
1421
                    $v .= chr(0x80 | ($c & 0x3F));
1422
                    $c = ($c >> 6) | 0x00010000;
1423
                    // fall-through
1424
                case $c >= 0x00000800:
1425
                    $v .= chr(0x80 | ($c & 0x3F));
1426
                    $c = ($c >> 6) | 0x00000800;
1427
                    // fall-through
1428
                case $c >= 0x00000080:
1429
                    $v .= chr(0x80 | ($c & 0x3F));
1430
                    $c = ($c >> 6) | 0x000000C0;
1431
                    // fall-through
1432
                default:
1433
                    $v .= chr($c);
1434
                    break;
1435
            }
1436
            $out .= strrev($v);
1437
        }
1438
        return $out;
1439
    }
1440
 
1441
    /**
1442
     * Extract raw BER from Base64 encoding
1443
     *
1444
     * @param string $str
1445
     * @return string
1446
     */
1447
    public static function extractBER($str)
1448
    {
1449
        /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
1450
         * above and beyond the ceritificate.
1451
         * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
1452
         *
1453
         * Bag Attributes
1454
         *     localKeyID: 01 00 00 00
1455
         * subject=/O=organization/OU=org unit/CN=common name
1456
         * issuer=/O=organization/CN=common name
1457
         */
1458
        if (strlen($str) > ini_get('pcre.backtrack_limit')) {
1459
            $temp = $str;
1460
        } else {
1461
            $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
1462
            $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1);
1463
        }
1464
        // remove new lines
1465
        $temp = str_replace(["\r", "\n", ' '], '', $temp);
1466
        // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
1467
        $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp);
1042 daniel-mar 1468
        $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false;
827 daniel-mar 1469
        return $temp != false ? $temp : $str;
1470
    }
1471
 
1472
    /**
1473
     * DER-encode the length
1474
     *
1475
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
1476
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
1477
     *
1478
     * @param int $length
1479
     * @return string
1480
     */
1481
    public static function encodeLength($length)
1482
    {
1483
        if ($length <= 0x7F) {
1484
            return chr($length);
1485
        }
1486
 
1487
        $temp = ltrim(pack('N', $length), chr(0));
1488
        return pack('Ca*', 0x80 | strlen($temp), $temp);
1489
    }
1490
 
1491
    /**
1492
     * Returns the OID corresponding to a name
1493
     *
1494
     * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
1495
     * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
1496
     * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
1497
     * to work from version to version.
1498
     *
1499
     * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
1500
     * what's being passed to it already is an OID and return that instead. A few examples.
1501
     *
1502
     * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
1503
     * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
1504
     * getOID('zzz') == 'zzz'
1505
     *
1506
     * @param string $name
1507
     * @return string
1508
     */
1509
    public static function getOID($name)
1510
    {
1511
        return isset(self::$reverseOIDs[$name]) ? self::$reverseOIDs[$name] : $name;
1512
    }
1513
}