4 * Pure-PHP ASN.1 Parser
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
12 * \phpseclib3\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
14 * Uses the 1988 ASN.1 syntax.
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
22 namespace phpseclib3\File;
24 use phpseclib3\Common\Functions\Strings;
25 use phpseclib3\File\ASN1\Element;
26 use phpseclib3\Math\BigInteger;
29 * Pure-PHP ASN.1 Parser
31 * @author Jim Wigginton <terrafrost@php.net>
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;
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;
49 const TYPE_OBJECT_IDENTIFIER = 6;
50 //const TYPE_OBJECT_DESCRIPTOR = 7;
51 //const TYPE_INSTANCE_OF = 8; // EXTERNAL
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
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;
77 // These tags are kinda place holders for other tags.
78 const TYPE_CHOICE = -1;
82 * ASN.1 object identifiers
85 * @link http://en.wikipedia.org/wiki/Object_identifier
87 private static $oids = [];
90 * ASN.1 object identifier reverse mapping
94 private static $reverseOIDs = [];
100 * @link http://php.net/class.datetime
102 private static $format = 'D, d M Y H:i:s O';
107 * If the mapping type is self::TYPE_ANY what do we actually encode it as?
110 * @see self::encode_der()
112 private static $filters;
115 * Current Location of most recent ASN.1 encode process
117 * Useful for debug purposes
120 * @see self::encode_der()
122 private static $location;
127 * In case we need to create ASN1\Element object's..
130 * @see self::decodeDER()
132 private static $encoded;
135 * Type mapping table for the ANY type.
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.
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'
169 * String type to character size mapping table.
171 * Non-convertable types are absent from this table.
172 * size == 0 indicates variable length encoding.
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,
189 * Serves a similar purpose to openssl's asn1parse
191 * @param Element|string $encoded
194 public static function decodeBER($encoded)
196 if ($encoded instanceof Element) {
197 $encoded = $encoded->element;
200 self::$encoded = $encoded;
202 $decoded = self::decode_ber($encoded);
203 if ($decoded === false) {
211 * Parse BER-encoding (Helper function)
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.
217 * @param string $encoded
219 * @param int $encoded_pos
222 private static function decode_ber($encoded, $start = 0, $encoded_pos = 0)
224 $current = ['start' => $start];
226 if (!isset($encoded[$encoded_pos])) {
229 $type = ord($encoded[$encoded_pos++]);
232 $constructed = ($type >> 5) & 1;
237 // process septets (since the eighth bit is ignored, it's not an octet)
239 if (!isset($encoded[$encoded_pos])) {
242 $temp = ord($encoded[$encoded_pos++]);
247 // "bits 7 to 1 of the first subsequent octet shall not all be zero"
248 if ($startOffset == 2 && $temp == 0) {
255 $start += $startOffset;
257 // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
258 if (!isset($encoded[$encoded_pos])) {
261 $length = ord($encoded[$encoded_pos++]);
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.
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];
276 extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
277 /** @var integer $length */
279 $current += ['headerlength' => 2];
282 if ($length > (strlen($encoded) - $encoded_pos)) {
286 $content = substr($encoded, $encoded_pos, $length);
289 // at this point $length can be overwritten. it's only accurate for definite length things as is
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.
299 -- http://www.obj-sys.com/asn1tutorial/node12.html */
300 $class = ($type >> 6) & 3;
302 case self::CLASS_APPLICATION:
303 case self::CLASS_PRIVATE:
304 case self::CLASS_CONTEXT_SPECIFIC:
309 'content' => $content,
310 'length' => $length + $start - $current['start']
315 $remainingLength = $length;
316 while ($remainingLength > 0) {
317 $temp = self::decode_ber($content, $start, $content_pos);
318 if ($temp === false) {
321 $length = $temp['length'];
322 // end-of-content octets - see paragraph 8.1.5
323 if (substr($content, $content_pos + $length, 2) == "\0\0") {
326 $newcontent[] = $temp;
330 $remainingLength -= $length;
331 $newcontent[] = $temp;
332 $content_pos += $length;
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']
347 $current += ['type' => $tag];
349 // decode UNIVERSAL tags
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) {
356 $current['content'] = (bool) ord($content[$content_pos]);
358 case self::TYPE_INTEGER:
359 case self::TYPE_ENUMERATED:
363 $current['content'] = new BigInteger(substr($content, $content_pos), -256);
365 case self::TYPE_REAL: // not currently supported
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
372 $current['content'] = substr($content, $content_pos);
374 $temp = self::decode_ber($content, $start, $content_pos);
375 if ($temp === false) {
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) {
385 $current['content'] .= substr($temp[$i]['content'], 1);
387 // all subtags should be bit strings
388 if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
391 $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
394 case self::TYPE_OCTET_STRING:
396 $current['content'] = substr($content, $content_pos);
398 $current['content'] = '';
400 while (substr($content, $content_pos, 2) != "\0\0") {
401 $temp = self::decode_ber($content, $length + $start, $content_pos);
402 if ($temp === false) {
405 $content_pos += $temp['length'];
406 // all subtags should be octet strings
407 if ($temp['type'] != self::TYPE_OCTET_STRING) {
410 $current['content'] .= $temp['content'];
411 $length += $temp['length'];
413 if (substr($content, $content_pos, 2) == "\0\0") {
414 $length += 2; // +2 for the EOC
418 case self::TYPE_NULL:
419 // "The contents octets shall not contain any octets." -- paragraph 8.8.2
420 if ($constructed || strlen($content)) {
424 case self::TYPE_SEQUENCE:
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
439 $temp = self::decode_ber($content, $start + $offset, $content_pos);
440 if ($temp === false) {
443 $content_pos += $temp['length'];
444 $current['content'][] = $temp;
445 $offset += $temp['length'];
448 case self::TYPE_OBJECT_IDENTIFIER:
452 $current['content'] = self::decodeOID(substr($content, $content_pos));
453 if ($current['content'] === false) {
457 /* Each character string type shall be encoded as if it had been declared:
458 [UNIVERSAL x] IMPLICIT OCTET STRING
460 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
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:
484 case self::TYPE_BMP_STRING:
488 $current['content'] = substr($content, $content_pos);
490 case self::TYPE_UTC_TIME:
491 case self::TYPE_GENERALIZED_TIME:
495 $current['content'] = self::decodeTime(substr($content, $content_pos), $tag);
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']];
510 * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
512 * "Special" mappings may be applied on a per tag-name basis via $special.
514 * @param array $decoded
515 * @param array $mapping
516 * @param array $special
517 * @return array|bool|Element|string|null
519 public static function asn1map(array $decoded, $mapping, $special = [])
521 if (isset($mapping['explicit']) && is_array($decoded['content'])) {
522 $decoded = $decoded['content'][0];
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']));
532 $inmap = self::ANY_MAP[$intype];
533 if (is_string($inmap)) {
534 return [$inmap => self::asn1map($decoded, ['type' => $intype] + $mapping, $special)];
537 case $mapping['type'] == self::TYPE_CHOICE:
538 foreach ($mapping['children'] as $key => $option) {
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);
544 case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
545 $v = self::asn1map($decoded, $option, $special);
551 if (isset($special[$key])) {
552 $value = $special[$key]($value);
554 return [$key => $value];
558 case isset($mapping['implicit']):
559 case isset($mapping['explicit']):
560 case $decoded['type'] == $mapping['type']:
563 // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
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:
574 if (isset($mapping['implicit'])) {
575 $decoded['type'] = $mapping['type'];
578 switch ($decoded['type']) {
579 case self::TYPE_SEQUENCE:
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) {
594 $n = count($decoded['content']);
597 foreach ($mapping['children'] as $key => $child) {
598 $maymatch = $i < $n; // Match only existing input.
600 $temp = $decoded['content'][$i];
602 if ($child['type'] != self::TYPE_CHOICE) {
603 // Get the mapping and input class & constant.
604 $childClass = $tempClass = self::CLASS_UNIVERSAL;
606 if (isset($temp['constant'])) {
607 $tempClass = $temp['type'];
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'];
617 if (isset($constant) && isset($temp['constant'])) {
618 // Can only match if constants and class match.
619 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
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;
628 // Attempt submapping.
629 $candidate = self::asn1map($temp, $child, $special);
630 $maymatch = $candidate !== null;
634 // Got the match: use it.
635 if (isset($special[$key])) {
636 $candidate = $special[$key]($candidate);
638 $map[$key] = $candidate;
640 } elseif (isset($child['default'])) {
641 $map[$key] = $child['default'];
642 } elseif (!isset($child['optional'])) {
643 return null; // Syntax error.
647 // Fail mapping if all input items have not been consumed.
648 return $i < $n ? null : $map;
650 // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
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) {
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'];
673 foreach ($mapping['children'] as $key => $child) {
674 if (isset($map[$key])) {
678 if ($child['type'] != self::TYPE_CHOICE) {
679 $childClass = self::CLASS_UNIVERSAL;
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'];
689 if (isset($constant) && isset($temp['constant'])) {
690 // Can only match if constants and class match.
691 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
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;
699 // Attempt submapping.
700 $candidate = self::asn1map($temp, $child, $special);
701 $maymatch = $candidate !== null;
708 // Got the match: use it.
709 if (isset($special[$key])) {
710 $candidate = $special[$key]($candidate);
712 $map[$key] = $candidate;
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'])) {
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'];
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']);
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;
747 From X.680-0207.pdf#page=46 (21.7):
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
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));
763 $map = array_reverse($mapping['mapping']);
764 foreach ($map as $i => $value) {
772 case self::TYPE_OCTET_STRING:
773 return $decoded['content'];
774 case self::TYPE_NULL:
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);
795 if (isset($mapping['mapping'])) {
796 $temp = (int) $temp->toString();
797 return isset($mapping['mapping'][$temp]) ?
798 $mapping['mapping'][$temp] :
806 * DER-decode the length
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.
811 * @param string $string
814 public static function decodeLength(&$string)
816 $length = ord(Strings::shift($string));
817 if ($length & 0x80) { // definite length, long form
819 $temp = Strings::shift($string, $length);
820 list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
828 * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
831 * "Special" mappings can be applied via $special.
833 * @param Element|string|array $source
834 * @param array $mapping
835 * @param array $special
838 public static function encodeDER($source, $mapping, $special = [])
840 self::$location = [];
841 return self::encode_der($source, $mapping, null, $special);
845 * ASN.1 Encode (Helper function)
847 * @param Element|string|array|null $source
848 * @param array $mapping
850 * @param array $special
853 private static function encode_der($source, array $mapping, $idx = null, array $special = [])
855 if ($source instanceof Element) {
856 return $source->element;
859 // do not encode (implicitly optional) fields with value set to default
860 if (isset($mapping['default']) && $source === $mapping['default']) {
865 if (isset($special[$idx])) {
866 $source = $special[$idx]($source);
868 self::$location[] = $idx;
871 $tag = $mapping['type'];
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
878 // ignore the min and max
879 if (isset($mapping['min']) && isset($mapping['max'])) {
881 $child = $mapping['children'];
883 foreach ($source as $content) {
884 $temp = self::encode_der($content, $child, null, $special);
885 if ($temp === false) {
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."
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) {
898 $value = implode('', $value);
903 foreach ($mapping['children'] as $key => $child) {
904 if (!array_key_exists($key, $source)) {
905 if (!isset($child['optional'])) {
911 $temp = self::encode_der($source[$key], $child, $key, $special);
912 if ($temp === false) {
916 // An empty child encoding means it has been optimized out.
917 // Else we should have at least one tag byte.
922 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
923 if (isset($child['constant'])) {
925 From X.680-0207.pdf#page=58 (30.6):
927 "The tagging construction specifies explicit tagging if any of the following holds:
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)."
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;
937 $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
938 $temp = $subtag . substr($temp, 1);
944 case self::TYPE_CHOICE:
947 foreach ($mapping['children'] as $key => $child) {
948 if (!isset($source[$key])) {
952 $temp = self::encode_der($source[$key], $child, $key, $special);
953 if ($temp === false) {
957 // An empty child encoding means it has been optimized out.
958 // Else we should have at least one tag byte.
963 $tag = ord($temp[0]);
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;
971 $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
972 $temp = $subtag . substr($temp, 1);
978 array_pop(self::$location);
981 if ($temp && isset($mapping['cast'])) {
982 $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
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);
992 $value = $source->toBytes(true);
994 $value = array_search($source, $mapping['mapping']);
995 if ($value === false) {
998 $value = new BigInteger($value);
999 $value = $value->toBytes(true);
1001 if (!strlen($value)) {
1005 case self::TYPE_UTC_TIME:
1006 case self::TYPE_GENERALIZED_TIME:
1007 $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
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';
1015 case self::TYPE_BIT_STRING:
1016 if (isset($mapping['mapping'])) {
1017 $bits = array_fill(0, count($mapping['mapping']), 0);
1019 for ($i = 0; $i < count($mapping['mapping']); $i++) {
1020 if (in_array($mapping['mapping'][$i], $source)) {
1026 if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
1027 $size = $mapping['min'] - 1;
1030 $offset = 8 - (($size + 1) & 7);
1031 $offset = $offset !== 8 ? $offset : 0;
1033 $value = chr($offset);
1035 for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
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));
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.
1052 -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
1055 case self::TYPE_OBJECT_IDENTIFIER:
1056 $value = self::encodeOID($source);
1058 case self::TYPE_ANY:
1059 $loc = self::$location;
1061 array_pop(self::$location);
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);
1082 $filters = self::$filters;
1083 foreach ($loc as $part) {
1084 if (!isset($filters[$part])) {
1088 $filters = $filters[$part];
1090 if ($filters === false) {
1091 throw new \RuntimeException('No filters defined for ' . implode('/', $loc));
1093 return self::encode_der($source, $filters + $mapping, null, $special);
1094 case self::TYPE_NULL:
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:
1110 case self::TYPE_BOOLEAN:
1111 $value = $source ? "\xFF" : "\x00";
1114 throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', self::$location));
1118 array_pop(self::$location);
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'];
1126 $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
1130 return chr($tag) . self::encodeLength(strlen($value)) . $value;
1134 * BER-decode the OID
1136 * Called by _decode_ber()
1138 * @param string $content
1141 public static function decodeOID($content)
1145 $eighty = new BigInteger(80);
1150 $len = strlen($content);
1151 // see https://github.com/openjdk/jdk/blob/2deb318c9f047ec5a4b160d66a4b52f93688ec42/src/java.base/share/classes/sun/security/util/ObjectIdentifier.java#L55
1153 //throw new \RuntimeException("Object identifier size is limited to 4096 bytes ($len bytes present)");
1157 if (ord($content[$len - 1]) & 0x80) {
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) {
1168 $n = new BigInteger();
1171 $part1 = array_shift($oid);
1172 $first = floor(ord($content[0]) / 40);
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."
1177 -- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22
1179 if ($first <= 2) { // ie. 0 <= ord($content[0]) < 120 (0x78)
1180 array_unshift($oid, ord($content[0]) % 40);
1181 array_unshift($oid, $first);
1183 array_unshift($oid, $part1->subtract($eighty));
1184 array_unshift($oid, 2);
1187 return implode('.', $oid);
1191 * DER-encode the OID
1193 * Called by _encode_der()
1195 * @param string $source
1198 public static function encodeOID($source)
1200 static $mask, $zero, $forty;
1202 $mask = new BigInteger(0x7F);
1203 $zero = new BigInteger();
1204 $forty = new BigInteger(40);
1207 if (!preg_match('#(?:\d+\.)+#', $source)) {
1208 $oid = isset(self::$reverseOIDs[$source]) ? self::$reverseOIDs[$source] : false;
1212 if ($oid === false) {
1213 throw new \RuntimeException('Invalid OID');
1216 $parts = explode('.', $oid);
1217 $part1 = array_shift($parts);
1218 $part2 = array_shift($parts);
1220 $first = new BigInteger($part1);
1221 $first = $first->multiply($forty);
1222 $first = $first->add(new BigInteger($part2));
1224 array_unshift($parts, $first->toString());
1227 foreach ($parts as $part) {
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);
1239 $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
1248 * BER-decode the time
1250 * Called by _decode_ber() and in the case of implicit tags asn1map().
1252 * @param string $content
1254 * @return \DateTime|false
1256 private static function decodeTime($content, $tag)
1259 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1260 http://www.obj-sys.com/asn1tutorial/node15.html
1263 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1264 http://www.obj-sys.com/asn1tutorial/node14.html */
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];
1275 $prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
1276 $content = $prefix . $content;
1277 } elseif (strpos($content, '.') !== false) {
1281 if ($content[strlen($content) - 1] == 'Z') {
1282 $content = substr($content, 0, -1) . '+0000';
1285 if (strpos($content, '-') !== false || strpos($content, '+') !== false) {
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);
1295 * Set the time format
1297 * Sets the time / date format for asn1map().
1299 * @param string $format
1301 public static function setTimeFormat($format)
1303 self::$format = $format;
1309 * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1310 * Previously loaded OIDs are retained.
1312 * @param array $oids
1314 public static function loadOIDs(array $oids)
1316 self::$reverseOIDs += $oids;
1317 self::$oids = array_flip(self::$reverseOIDs);
1323 * See \phpseclib3\File\X509, etc, for an example.
1324 * Previously loaded filters are not retained.
1326 * @param array $filters
1328 public static function setFilters(array $filters)
1330 self::$filters = $filters;
1334 * String type conversion
1336 * This is a lazy conversion, dealing only with character size.
1337 * No real conversion table is used.
1344 public static function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
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)) {
1350 $insize = self::STRING_TYPE_SIZE[$from];
1351 $outsize = self::STRING_TYPE_SIZE[$to];
1352 $inlength = strlen($in);
1355 for ($i = 0; $i < $inlength;) {
1356 if ($inlength - $i < $insize) {
1360 // Get an input character as a 32-bit value.
1361 $c = ord($in[$i++]);
1364 $c = ($c << 8) | ord($in[$i++]);
1365 $c = ($c << 8) | ord($in[$i++]);
1368 $c = ($c << 8) | ord($in[$i++]);
1372 case ($c & 0x80) == 0x00:
1374 case ($c & 0x40) == 0x00:
1379 if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1382 $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1385 } while ($c & $bit);
1390 // Convert and append the character to output string.
1394 $v .= chr($c & 0xFF);
1396 $v .= chr($c & 0xFF);
1400 $v .= chr($c & 0xFF);
1404 $v .= chr($c & 0xFF);
1410 case ($c & (PHP_INT_SIZE == 8 ? 0x80000000 : (1 << 31))) != 0:
1412 case $c >= 0x04000000:
1413 $v .= chr(0x80 | ($c & 0x3F));
1414 $c = ($c >> 6) | 0x04000000;
1416 case $c >= 0x00200000:
1417 $v .= chr(0x80 | ($c & 0x3F));
1418 $c = ($c >> 6) | 0x00200000;
1420 case $c >= 0x00010000:
1421 $v .= chr(0x80 | ($c & 0x3F));
1422 $c = ($c >> 6) | 0x00010000;
1424 case $c >= 0x00000800:
1425 $v .= chr(0x80 | ($c & 0x3F));
1426 $c = ($c >> 6) | 0x00000800;
1428 case $c >= 0x00000080:
1429 $v .= chr(0x80 | ($c & 0x3F));
1430 $c = ($c >> 6) | 0x000000C0;
1442 * Extract raw BER from Base64 encoding
1444 * @param string $str
1447 public static function extractBER($str)
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:
1454 * localKeyID: 01 00 00 00
1455 * subject=/O=organization/OU=org unit/CN=common name
1456 * issuer=/O=organization/CN=common name
1458 if (strlen($str) > ini_get('pcre.backtrack_limit')) {
1461 $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
1462 $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1);
1465 $temp = str_replace(["\r", "\n", ' '], '', $temp);
1466 // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
1467 $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp);
1468 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false;
1469 return $temp != false ? $temp : $str;
1473 * DER-encode the length
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.
1478 * @param int $length
1481 public static function encodeLength($length)
1483 if ($length <= 0x7F) {
1484 return chr($length);
1487 $temp = ltrim(pack('N', $length), chr(0));
1488 return pack('Ca*', 0x80 | strlen($temp), $temp);
1492 * Returns the OID corresponding to a name
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.
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.
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'
1506 * @param string $name
1509 public static function getOID($name)
1511 return isset(self::$reverseOIDs[$name]) ? self::$reverseOIDs[$name] : $name;