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 * \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
14 * Uses the 1988 ASN.1 syntax.
18 * @author Jim Wigginton <terrafrost@php.net>
19 * @copyright 2012 Jim Wigginton
20 * @license http://www.opensource.org/licenses/mit-license.html MIT License
21 * @link http://phpseclib.sourceforge.net
24 namespace phpseclib\File;
26 use ParagonIE\ConstantTime\Base64;
27 use phpseclib\File\ASN1\Element;
28 use phpseclib\Math\BigInteger;
31 * Pure-PHP ASN.1 Parser
34 * @author Jim Wigginton <terrafrost@php.net>
43 * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
45 const CLASS_UNIVERSAL = 0;
46 const CLASS_APPLICATION = 1;
47 const CLASS_CONTEXT_SPECIFIC = 2;
48 const CLASS_PRIVATE = 3;
55 * @link http://www.obj-sys.com/asn1tutorial/node124.html
57 const TYPE_BOOLEAN = 1;
58 const TYPE_INTEGER = 2;
59 const TYPE_BIT_STRING = 3;
60 const TYPE_OCTET_STRING = 4;
62 const TYPE_OBJECT_IDENTIFIER = 6;
63 //const TYPE_OBJECT_DESCRIPTOR = 7;
64 //const TYPE_INSTANCE_OF = 8; // EXTERNAL
66 const TYPE_ENUMERATED = 10;
67 //const TYPE_EMBEDDED = 11;
68 const TYPE_UTF8_STRING = 12;
69 //const TYPE_RELATIVE_OID = 13;
70 const TYPE_SEQUENCE = 16; // SEQUENCE OF
71 const TYPE_SET = 17; // SET OF
77 * @link http://www.obj-sys.com/asn1tutorial/node10.html
79 const TYPE_NUMERIC_STRING = 18;
80 const TYPE_PRINTABLE_STRING = 19;
81 const TYPE_TELETEX_STRING = 20; // T61String
82 const TYPE_VIDEOTEX_STRING = 21;
83 const TYPE_IA5_STRING = 22;
84 const TYPE_UTC_TIME = 23;
85 const TYPE_GENERALIZED_TIME = 24;
86 const TYPE_GRAPHIC_STRING = 25;
87 const TYPE_VISIBLE_STRING = 26; // ISO646String
88 const TYPE_GENERAL_STRING = 27;
89 const TYPE_UNIVERSAL_STRING = 28;
90 //const TYPE_CHARACTER_STRING = 29;
91 const TYPE_BMP_STRING = 30;
97 * These tags are kinda place holders for other tags.
101 const TYPE_CHOICE = -1;
106 * ASN.1 object identifier
110 * @link http://en.wikipedia.org/wiki/Object_identifier
115 * Default date format
119 * @link http://php.net/class.datetime
121 var $format = 'D, d M Y H:i:s O';
124 * Default date format
128 * @see self::setTimeFormat()
129 * @see self::asn1map()
130 * @link http://php.net/class.datetime
137 * If the mapping type is self::TYPE_ANY what do we actually encode it as?
141 * @see self::_encode_der()
146 * Type mapping table for the ANY type.
148 * Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element.
149 * Unambiguous types get the direct mapping (int/real/bool).
150 * Others are mapped as a choice, with an extra indexing level.
156 self::TYPE_BOOLEAN => true,
157 self::TYPE_INTEGER => true,
158 self::TYPE_BIT_STRING => 'bitString',
159 self::TYPE_OCTET_STRING => 'octetString',
160 self::TYPE_NULL => 'null',
161 self::TYPE_OBJECT_IDENTIFIER => 'objectIdentifier',
162 self::TYPE_REAL => true,
163 self::TYPE_ENUMERATED => 'enumerated',
164 self::TYPE_UTF8_STRING => 'utf8String',
165 self::TYPE_NUMERIC_STRING => 'numericString',
166 self::TYPE_PRINTABLE_STRING => 'printableString',
167 self::TYPE_TELETEX_STRING => 'teletexString',
168 self::TYPE_VIDEOTEX_STRING => 'videotexString',
169 self::TYPE_IA5_STRING => 'ia5String',
170 self::TYPE_UTC_TIME => 'utcTime',
171 self::TYPE_GENERALIZED_TIME => 'generalTime',
172 self::TYPE_GRAPHIC_STRING => 'graphicString',
173 self::TYPE_VISIBLE_STRING => 'visibleString',
174 self::TYPE_GENERAL_STRING => 'generalString',
175 self::TYPE_UNIVERSAL_STRING => 'universalString',
176 //self::TYPE_CHARACTER_STRING => 'characterString',
177 self::TYPE_BMP_STRING => 'bmpString'
181 * String type to character size mapping table.
183 * Non-convertable types are absent from this table.
184 * size == 0 indicates variable length encoding.
189 var $stringTypeSize = array(
190 self::TYPE_UTF8_STRING => 0,
191 self::TYPE_BMP_STRING => 2,
192 self::TYPE_UNIVERSAL_STRING => 4,
193 self::TYPE_PRINTABLE_STRING => 1,
194 self::TYPE_TELETEX_STRING => 1,
195 self::TYPE_IA5_STRING => 1,
196 self::TYPE_VISIBLE_STRING => 1,
202 * Serves a similar purpose to openssl's asn1parse
204 * @param string $encoded
208 function decodeBER($encoded)
210 if ($encoded instanceof Element) {
211 $encoded = $encoded->element;
214 $this->encoded = $encoded;
215 // encapsulate in an array for BC with the old decodeBER
216 return array($this->_decode_ber($encoded));
220 * Parse BER-encoding (Helper function)
222 * Sometimes we want to get the BER encoding of a particular tag. $start lets us do that without having to reencode.
223 * $encoded is passed by reference for the recursive calls done for self::TYPE_BIT_STRING and
224 * self::TYPE_OCTET_STRING. In those cases, the indefinite length is used.
226 * @param string $encoded
231 function _decode_ber($encoded, $start = 0)
233 $current = array('start' => $start);
235 $type = ord($this->_string_shift($encoded));
238 $constructed = ($type >> 5) & 1;
243 // process septets (since the eighth bit is ignored, it's not an octet)
245 $loop = ord($encoded[0]) >> 7;
247 $tag |= ord($this->_string_shift($encoded)) & 0x7F;
252 // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
253 $length = ord($this->_string_shift($encoded));
255 if ($length == 0x80) { // indefinite length
256 // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
257 // immediately available." -- paragraph 8.1.3.2.c
258 $length = strlen($encoded);
259 } elseif ($length & 0x80) { // definite length, long form
260 // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
261 // support it up to four.
263 $temp = $this->_string_shift($encoded, $length);
264 // tags of indefinte length don't really have a header length; this length includes the tag
265 $current+= array('headerlength' => $length + 2);
267 extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
269 $current+= array('headerlength' => 2);
272 if ($length > strlen($encoded)) {
276 $content = $this->_string_shift($encoded, $length);
278 // at this point $length can be overwritten. it's only accurate for definite length things as is
280 /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
281 built-in types. It defines an application-independent data type that must be distinguishable from all other
282 data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
283 have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
284 a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
285 alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
286 data type; the term CONTEXT-SPECIFIC does not appear.
288 -- http://www.obj-sys.com/asn1tutorial/node12.html */
289 $class = ($type >> 6) & 3;
291 case self::CLASS_APPLICATION:
292 case self::CLASS_PRIVATE:
293 case self::CLASS_CONTEXT_SPECIFIC:
298 'content' => $content,
299 'length' => $length + $start - $current['start']
303 $newcontent = array();
304 $remainingLength = $length;
305 while ($remainingLength > 0) {
306 $temp = $this->_decode_ber($content, $start);
307 $length = $temp['length'];
308 // end-of-content octets - see paragraph 8.1.5
309 if (substr($content, $length, 2) == "\0\0") {
312 $newcontent[] = $temp;
316 $remainingLength-= $length;
317 $newcontent[] = $temp;
318 $this->_string_shift($content, $length);
324 // the array encapsulation is for BC with the old format
325 'content' => $newcontent,
326 // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
327 // the absence of $content['headerlength'] is how we know if something is indefinite or not.
328 // technically, it could be defined to be 2 and then another indicator could be used but whatever.
329 'length' => $start - $current['start']
333 $current+= array('type' => $tag);
335 // decode UNIVERSAL tags
337 case self::TYPE_BOOLEAN:
338 // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
339 //if (strlen($content) != 1) {
342 $current['content'] = (bool) ord($content[0]);
344 case self::TYPE_INTEGER:
345 case self::TYPE_ENUMERATED:
346 $current['content'] = new BigInteger($content, -256);
348 case self::TYPE_REAL: // not currently supported
350 case self::TYPE_BIT_STRING:
351 // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
352 // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
355 $current['content'] = $content;
357 $temp = $this->_decode_ber($content, $start);
358 $length-= strlen($content);
359 $last = count($temp) - 1;
360 for ($i = 0; $i < $last; $i++) {
361 // all subtags should be bit strings
362 //if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
365 $current['content'].= substr($temp[$i]['content'], 1);
367 // all subtags should be bit strings
368 //if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
371 $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
374 case self::TYPE_OCTET_STRING:
376 $current['content'] = $content;
378 $current['content'] = '';
380 while (substr($content, 0, 2) != "\0\0") {
381 $temp = $this->_decode_ber($content, $length + $start);
382 $this->_string_shift($content, $temp['length']);
383 // all subtags should be octet strings
384 //if ($temp['type'] != self::TYPE_OCTET_STRING) {
387 $current['content'].= $temp['content'];
388 $length+= $temp['length'];
390 if (substr($content, 0, 2) == "\0\0") {
391 $length+= 2; // +2 for the EOC
395 case self::TYPE_NULL:
396 // "The contents octets shall not contain any octets." -- paragraph 8.8.2
397 //if (strlen($content)) {
401 case self::TYPE_SEQUENCE:
404 $current['content'] = array();
405 while (strlen($content)) {
406 // if indefinite length construction was used and we have an end-of-content string next
407 // 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
408 if (!isset($current['headerlength']) && substr($content, 0, 2) == "\0\0") {
409 $length = $offset + 2; // +2 for the EOC
412 $temp = $this->_decode_ber($content, $start + $offset);
413 $this->_string_shift($content, $temp['length']);
414 $current['content'][] = $temp;
415 $offset+= $temp['length'];
418 case self::TYPE_OBJECT_IDENTIFIER:
419 $temp = ord($this->_string_shift($content));
420 $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
423 while (strlen($content)) {
424 $temp = ord($this->_string_shift($content));
426 $valuen |= $temp & 0x7F;
428 $current['content'].= ".$valuen";
432 // the eighth bit of the last byte should not be 1
437 /* Each character string type shall be encoded as if it had been declared:
438 [UNIVERSAL x] IMPLICIT OCTET STRING
440 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
442 Per that, we're not going to do any validation. If there are any illegal characters in the string,
443 we don't really care */
444 case self::TYPE_NUMERIC_STRING:
445 // 0,1,2,3,4,5,6,7,8,9, and space
446 case self::TYPE_PRINTABLE_STRING:
447 // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
448 // hyphen, full stop, solidus, colon, equal sign, question mark
449 case self::TYPE_TELETEX_STRING:
450 // The Teletex character set in CCITT's T61, space, and delete
451 // see http://en.wikipedia.org/wiki/Teletex#Character_sets
452 case self::TYPE_VIDEOTEX_STRING:
453 // The Videotex character set in CCITT's T.100 and T.101, space, and delete
454 case self::TYPE_VISIBLE_STRING:
455 // Printing character sets of international ASCII, and space
456 case self::TYPE_IA5_STRING:
457 // International Alphabet 5 (International ASCII)
458 case self::TYPE_GRAPHIC_STRING:
459 // All registered G sets, and space
460 case self::TYPE_GENERAL_STRING:
461 // All registered C and G sets, space and delete
462 case self::TYPE_UTF8_STRING:
464 case self::TYPE_BMP_STRING:
465 $current['content'] = $content;
467 case self::TYPE_UTC_TIME:
468 case self::TYPE_GENERALIZED_TIME:
469 $current['content'] = $this->_decodeTime($content, $tag);
475 // ie. length is the length of the full TLV encoding - it's not just the length of the value
476 return $current + array('length' => $start - $current['start']);
482 * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
484 * "Special" mappings may be applied on a per tag-name basis via $special.
486 * @param array $decoded
487 * @param array $mapping
488 * @param array $special
492 function asn1map($decoded, $mapping, $special = array())
494 if (isset($mapping['explicit']) && is_array($decoded['content'])) {
495 $decoded = $decoded['content'][0];
499 case $mapping['type'] == self::TYPE_ANY:
500 $intype = $decoded['type'];
501 if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ($this->encoded[$decoded['start']] & 0x20)) {
502 return new Element(substr($this->encoded, $decoded['start'], $decoded['length']));
504 $inmap = $this->ANYmap[$intype];
505 if (is_string($inmap)) {
506 return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
509 case $mapping['type'] == self::TYPE_CHOICE:
510 foreach ($mapping['children'] as $key => $option) {
512 case isset($option['constant']) && $option['constant'] == $decoded['constant']:
513 case !isset($option['constant']) && $option['type'] == $decoded['type']:
514 $value = $this->asn1map($decoded, $option, $special);
516 case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
517 $v = $this->asn1map($decoded, $option, $special);
523 if (isset($special[$key])) {
524 $value = call_user_func($special[$key], $value);
526 return array($key => $value);
530 case isset($mapping['implicit']):
531 case isset($mapping['explicit']):
532 case $decoded['type'] == $mapping['type']:
535 // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
538 case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18
539 case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30
540 case $mapping['type'] < 18:
541 case $mapping['type'] > 30:
546 if (isset($mapping['implicit'])) {
547 $decoded['type'] = $mapping['type'];
550 switch ($decoded['type']) {
551 case self::TYPE_SEQUENCE:
554 // ignore the min and max
555 if (isset($mapping['min']) && isset($mapping['max'])) {
556 $child = $mapping['children'];
557 foreach ($decoded['content'] as $content) {
558 if (($map[] = $this->asn1map($content, $child, $special)) === null) {
566 $n = count($decoded['content']);
569 foreach ($mapping['children'] as $key => $child) {
570 $maymatch = $i < $n; // Match only existing input.
572 $temp = $decoded['content'][$i];
574 if ($child['type'] != self::TYPE_CHOICE) {
575 // Get the mapping and input class & constant.
576 $childClass = $tempClass = self::CLASS_UNIVERSAL;
578 if (isset($temp['constant'])) {
579 $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC;
581 if (isset($child['class'])) {
582 $childClass = $child['class'];
583 $constant = $child['cast'];
584 } elseif (isset($child['constant'])) {
585 $childClass = self::CLASS_CONTEXT_SPECIFIC;
586 $constant = $child['constant'];
589 if (isset($constant) && isset($temp['constant'])) {
590 // Can only match if constants and class match.
591 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
593 // Can only match if no constant expected and type matches or is generic.
594 $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
600 // Attempt submapping.
601 $candidate = $this->asn1map($temp, $child, $special);
602 $maymatch = $candidate !== null;
606 // Got the match: use it.
607 if (isset($special[$key])) {
608 $candidate = call_user_func($special[$key], $candidate);
610 $map[$key] = $candidate;
612 } elseif (isset($child['default'])) {
613 $map[$key] = $child['default']; // Use default.
614 } elseif (!isset($child['optional'])) {
615 return null; // Syntax error.
619 // Fail mapping if all input items have not been consumed.
620 return $i < $n ? null: $map;
622 // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
626 // ignore the min and max
627 if (isset($mapping['min']) && isset($mapping['max'])) {
628 $child = $mapping['children'];
629 foreach ($decoded['content'] as $content) {
630 if (($map[] = $this->asn1map($content, $child, $special)) === null) {
638 for ($i = 0; $i < count($decoded['content']); $i++) {
639 $temp = $decoded['content'][$i];
640 $tempClass = self::CLASS_UNIVERSAL;
641 if (isset($temp['constant'])) {
642 $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC;
645 foreach ($mapping['children'] as $key => $child) {
646 if (isset($map[$key])) {
650 if ($child['type'] != self::TYPE_CHOICE) {
651 $childClass = self::CLASS_UNIVERSAL;
653 if (isset($child['class'])) {
654 $childClass = $child['class'];
655 $constant = $child['cast'];
656 } elseif (isset($child['constant'])) {
657 $childClass = self::CLASS_CONTEXT_SPECIFIC;
658 $constant = $child['constant'];
661 if (isset($constant) && isset($temp['constant'])) {
662 // Can only match if constants and class match.
663 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
665 // Can only match if no constant expected and type matches or is generic.
666 $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
671 // Attempt submapping.
672 $candidate = $this->asn1map($temp, $child, $special);
673 $maymatch = $candidate !== null;
680 // Got the match: use it.
681 if (isset($special[$key])) {
682 $candidate = call_user_func($special[$key], $candidate);
684 $map[$key] = $candidate;
689 foreach ($mapping['children'] as $key => $child) {
690 if (!isset($map[$key])) {
691 if (isset($child['default'])) {
692 $map[$key] = $child['default'];
693 } elseif (!isset($child['optional'])) {
699 case self::TYPE_OBJECT_IDENTIFIER:
700 return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
701 case self::TYPE_UTC_TIME:
702 case self::TYPE_GENERALIZED_TIME:
703 if (isset($mapping['implicit'])) {
704 $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
706 return @date($this->format, $decoded['content']);
707 case self::TYPE_BIT_STRING:
708 if (isset($mapping['mapping'])) {
709 $offset = ord($decoded['content'][0]);
710 $size = (strlen($decoded['content']) - 1) * 8 - $offset;
712 From X.680-0207.pdf#page=46 (21.7):
714 "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
715 arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
716 therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
719 $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
720 for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
721 $current = ord($decoded['content'][$i]);
722 for ($j = $offset; $j < 8; $j++) {
723 $bits[] = (bool) ($current & (1 << $j));
728 $map = array_reverse($mapping['mapping']);
729 foreach ($map as $i => $value) {
736 case self::TYPE_OCTET_STRING:
737 return Base64::encode($decoded['content']);
738 case self::TYPE_NULL:
740 case self::TYPE_BOOLEAN:
741 return $decoded['content'];
742 case self::TYPE_NUMERIC_STRING:
743 case self::TYPE_PRINTABLE_STRING:
744 case self::TYPE_TELETEX_STRING:
745 case self::TYPE_VIDEOTEX_STRING:
746 case self::TYPE_IA5_STRING:
747 case self::TYPE_GRAPHIC_STRING:
748 case self::TYPE_VISIBLE_STRING:
749 case self::TYPE_GENERAL_STRING:
750 case self::TYPE_UNIVERSAL_STRING:
751 case self::TYPE_UTF8_STRING:
752 case self::TYPE_BMP_STRING:
753 return $decoded['content'];
754 case self::TYPE_INTEGER:
755 case self::TYPE_ENUMERATED:
756 $temp = $decoded['content'];
757 if (isset($mapping['implicit'])) {
758 $temp = new BigInteger($decoded['content'], -256);
760 if (isset($mapping['mapping'])) {
761 $temp = (int) $temp->toString();
762 return isset($mapping['mapping'][$temp]) ?
763 $mapping['mapping'][$temp] :
773 * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
776 * "Special" mappings can be applied via $special.
778 * @param string $source
779 * @param string $mapping
784 function encodeDER($source, $mapping, $special = array())
786 $this->location = array();
787 return $this->_encode_der($source, $mapping, null, $special);
791 * ASN.1 Encode (Helper function)
793 * @param string $source
794 * @param string $mapping
797 * @throws \RuntimeException if the input has an error in it
800 function _encode_der($source, $mapping, $idx = null, $special = array())
802 if ($source instanceof Element) {
803 return $source->element;
806 // do not encode (implicitly optional) fields with value set to default
807 if (isset($mapping['default']) && $source === $mapping['default']) {
812 if (isset($special[$idx])) {
813 $source = call_user_func($special[$idx], $source);
815 $this->location[] = $idx;
818 $tag = $mapping['type'];
821 case self::TYPE_SET: // Children order is not important, thus process in sequence.
822 case self::TYPE_SEQUENCE:
823 $tag|= 0x20; // set the constructed bit
826 // ignore the min and max
827 if (isset($mapping['min']) && isset($mapping['max'])) {
828 $child = $mapping['children'];
830 foreach ($source as $content) {
831 $temp = $this->_encode_der($content, $child, null, $special);
832 if ($temp === false) {
840 foreach ($mapping['children'] as $key => $child) {
841 if (!array_key_exists($key, $source)) {
842 if (!isset($child['optional'])) {
848 $temp = $this->_encode_der($source[$key], $child, $key, $special);
849 if ($temp === false) {
853 // An empty child encoding means it has been optimized out.
854 // Else we should have at least one tag byte.
859 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
860 if (isset($child['constant'])) {
862 From X.680-0207.pdf#page=58 (30.6):
864 "The tagging construction specifies explicit tagging if any of the following holds:
866 c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
867 AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
868 an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
870 if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
871 $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
872 $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
874 $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
875 $temp = $subtag . substr($temp, 1);
881 case self::TYPE_CHOICE:
884 foreach ($mapping['children'] as $key => $child) {
885 if (!isset($source[$key])) {
889 $temp = $this->_encode_der($source[$key], $child, $key, $special);
890 if ($temp === false) {
894 // An empty child encoding means it has been optimized out.
895 // Else we should have at least one tag byte.
900 $tag = ord($temp[0]);
902 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
903 if (isset($child['constant'])) {
904 if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
905 $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
906 $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
908 $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
909 $temp = $subtag . substr($temp, 1);
915 array_pop($this->location);
918 if ($temp && isset($mapping['cast'])) {
919 $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
923 case self::TYPE_INTEGER:
924 case self::TYPE_ENUMERATED:
925 if (!isset($mapping['mapping'])) {
926 if (is_numeric($source)) {
927 $source = new BigInteger($source);
929 $value = $source->toBytes(true);
931 $value = array_search($source, $mapping['mapping']);
932 if ($value === false) {
935 $value = new BigInteger($value);
936 $value = $value->toBytes(true);
938 if (!strlen($value)) {
942 case self::TYPE_UTC_TIME:
943 case self::TYPE_GENERALIZED_TIME:
944 $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
946 $value = @gmdate($format, strtotime($source)) . 'Z';
948 case self::TYPE_BIT_STRING:
949 if (isset($mapping['mapping'])) {
950 $bits = array_fill(0, count($mapping['mapping']), 0);
952 for ($i = 0; $i < count($mapping['mapping']); $i++) {
953 if (in_array($mapping['mapping'][$i], $source)) {
959 if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
960 $size = $mapping['min'] - 1;
963 $offset = 8 - (($size + 1) & 7);
964 $offset = $offset !== 8 ? $offset : 0;
966 $value = chr($offset);
968 for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
972 $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
973 $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
974 foreach ($bytes as $byte) {
975 $value.= chr(bindec($byte));
980 case self::TYPE_OCTET_STRING:
981 /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
982 the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
984 -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
985 $value = Base64::decode($source);
987 case self::TYPE_OBJECT_IDENTIFIER:
988 $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
989 if ($oid === false) {
990 throw new \RuntimeException('Invalid OID');
994 $parts = explode('.', $oid);
995 $value = chr(40 * $parts[0] + $parts[1]);
996 for ($i = 2; $i < count($parts); $i++) {
1001 while ($parts[$i]) {
1002 $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
1005 $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
1010 case self::TYPE_ANY:
1011 $loc = $this->location;
1013 array_pop($this->location);
1017 case !isset($source):
1018 return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special);
1019 case is_int($source):
1020 case $source instanceof BigInteger:
1021 return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special);
1022 case is_float($source):
1023 return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special);
1024 case is_bool($source):
1025 return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special);
1026 case is_array($source) && count($source) == 1:
1027 $typename = implode('', array_keys($source));
1028 $outtype = array_search($typename, $this->ANYmap, true);
1029 if ($outtype !== false) {
1030 return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
1034 $filters = $this->filters;
1035 foreach ($loc as $part) {
1036 if (!isset($filters[$part])) {
1040 $filters = $filters[$part];
1042 if ($filters === false) {
1043 throw new \RuntimeException('No filters defined for ' . implode('/', $loc));
1046 return $this->_encode_der($source, $filters + $mapping, null, $special);
1047 case self::TYPE_NULL:
1050 case self::TYPE_NUMERIC_STRING:
1051 case self::TYPE_TELETEX_STRING:
1052 case self::TYPE_PRINTABLE_STRING:
1053 case self::TYPE_UNIVERSAL_STRING:
1054 case self::TYPE_UTF8_STRING:
1055 case self::TYPE_BMP_STRING:
1056 case self::TYPE_IA5_STRING:
1057 case self::TYPE_VISIBLE_STRING:
1058 case self::TYPE_VIDEOTEX_STRING:
1059 case self::TYPE_GRAPHIC_STRING:
1060 case self::TYPE_GENERAL_STRING:
1063 case self::TYPE_BOOLEAN:
1064 $value = $source ? "\xFF" : "\x00";
1067 throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', $this->location));
1072 array_pop($this->location);
1075 if (isset($mapping['cast'])) {
1076 if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
1077 $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1078 $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
1080 $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
1084 return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1088 * DER-encode the length
1090 * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
1091 * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
1094 * @param int $length
1097 function _encodeLength($length)
1099 if ($length <= 0x7F) {
1100 return chr($length);
1103 $temp = ltrim(pack('N', $length), chr(0));
1104 return pack('Ca*', 0x80 | strlen($temp), $temp);
1108 * BER-decode the time
1110 * Called by _decode_ber() and in the case of implicit tags asn1map().
1113 * @param string $content
1117 function _decodeTime($content, $tag)
1120 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1121 http://www.obj-sys.com/asn1tutorial/node15.html
1124 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1125 http://www.obj-sys.com/asn1tutorial/node14.html */
1127 $pattern = $tag == self::TYPE_UTC_TIME ?
1128 '#(..)(..)(..)(..)(..)(..)(.*)#' :
1129 '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';
1131 preg_match($pattern, $content, $matches);
1133 list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;
1135 if ($tag == self::TYPE_UTC_TIME) {
1136 $year = $year >= 50 ? "19$year" : "20$year";
1139 if ($timezone == 'Z') {
1140 $mktime = 'gmmktime';
1142 } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
1143 $mktime = 'gmmktime';
1144 $timezone = 60 * $matches[3] + 3600 * $matches[2];
1145 if ($matches[1] == '-') {
1146 $timezone = -$timezone;
1153 return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
1157 * Set the time format
1159 * Sets the time / date format for asn1map().
1162 * @param string $format
1164 function setTimeFormat($format)
1166 $this->format = $format;
1172 * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1175 * @param array $oids
1177 function loadOIDs($oids)
1179 $this->oids = $oids;
1185 * See \phpseclib\File\X509, etc, for an example.
1188 * @param array $filters
1190 function loadFilters($filters)
1192 $this->filters = $filters;
1198 * Inspired by array_shift
1200 * @param string $string
1205 function _string_shift(&$string, $index = 1)
1207 $substr = substr($string, 0, $index);
1208 $string = substr($string, $index);
1213 * String type conversion
1215 * This is a lazy conversion, dealing only with character size.
1216 * No real conversion table is used.
1224 function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
1226 if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1229 $insize = $this->stringTypeSize[$from];
1230 $outsize = $this->stringTypeSize[$to];
1231 $inlength = strlen($in);
1234 for ($i = 0; $i < $inlength;) {
1235 if ($inlength - $i < $insize) {
1239 // Get an input character as a 32-bit value.
1240 $c = ord($in[$i++]);
1243 $c = ($c << 8) | ord($in[$i++]);
1244 $c = ($c << 8) | ord($in[$i++]);
1246 $c = ($c << 8) | ord($in[$i++]);
1249 case ($c & 0x80) == 0x00:
1251 case ($c & 0x40) == 0x00:
1256 if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1259 $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1262 } while ($c & $bit);
1267 // Convert and append the character to output string.
1271 $v .= chr($c & 0xFF);
1273 $v .= chr($c & 0xFF);
1276 $v .= chr($c & 0xFF);
1279 $v .= chr($c & 0xFF);
1285 case ($c & 0x80000000) != 0:
1287 case $c >= 0x04000000:
1288 $v .= chr(0x80 | ($c & 0x3F));
1289 $c = ($c >> 6) | 0x04000000;
1290 case $c >= 0x00200000:
1291 $v .= chr(0x80 | ($c & 0x3F));
1292 $c = ($c >> 6) | 0x00200000;
1293 case $c >= 0x00010000:
1294 $v .= chr(0x80 | ($c & 0x3F));
1295 $c = ($c >> 6) | 0x00010000;
1296 case $c >= 0x00000800:
1297 $v .= chr(0x80 | ($c & 0x3F));
1298 $c = ($c >> 6) | 0x00000800;
1299 case $c >= 0x00000080:
1300 $v .= chr(0x80 | ($c & 0x3F));
1301 $c = ($c >> 6) | 0x000000C0;