2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5 * Pure-PHP ASN.1 Parser
9 * ASN.1 provides the semantics for data encoded using various schemes. The most commonly
10 * utilized scheme is DER or the "Distinguished Encoding Rules". PEM's are base64 encoded
13 * File_ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
15 * Uses the 1988 ASN.1 syntax.
17 * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
18 * of this software and associated documentation files (the "Software"), to deal
19 * in the Software without restriction, including without limitation the rights
20 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 * copies of the Software, and to permit persons to whom the Software is
22 * furnished to do so, subject to the following conditions:
24 * The above copyright notice and this permission notice shall be included in
25 * all copies or substantial portions of the Software.
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
37 * @author Jim Wigginton <terrafrost@php.net>
38 * @copyright MMXII Jim Wigginton
39 * @license http://www.opensource.org/licenses/mit-license.html MIT License
41 * @link http://phpseclib.sourceforge.net
45 * Include Math_BigInteger
47 if (!class_exists('Math_BigInteger')) {
48 require_once('Math/BigInteger.php');
55 * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
57 define('FILE_ASN1_CLASS_UNIVERSAL', 0);
58 define('FILE_ASN1_CLASS_APPLICATION', 1);
59 define('FILE_ASN1_CLASS_CONTEXT_SPECIFIC', 2);
60 define('FILE_ASN1_CLASS_PRIVATE', 3);
67 * @link http://www.obj-sys.com/asn1tutorial/node124.html
69 define('FILE_ASN1_TYPE_BOOLEAN', 1);
70 define('FILE_ASN1_TYPE_INTEGER', 2);
71 define('FILE_ASN1_TYPE_BIT_STRING', 3);
72 define('FILE_ASN1_TYPE_OCTET_STRING', 4);
73 define('FILE_ASN1_TYPE_NULL', 5);
74 define('FILE_ASN1_TYPE_OBJECT_IDENTIFIER',6);
75 //define('FILE_ASN1_TYPE_OBJECT_DESCRIPTOR',7);
76 //define('FILE_ASN1_TYPE_INSTANCE_OF', 8); // EXTERNAL
77 define('FILE_ASN1_TYPE_REAL', 9);
78 define('FILE_ASN1_TYPE_ENUMERATED', 10);
79 //define('FILE_ASN1_TYPE_EMBEDDED', 11);
80 define('FILE_ASN1_TYPE_UTF8_STRING', 12);
81 //define('FILE_ASN1_TYPE_RELATIVE_OID', 13);
82 define('FILE_ASN1_TYPE_SEQUENCE', 16); // SEQUENCE OF
83 define('FILE_ASN1_TYPE_SET', 17); // SET OF
89 * @link http://www.obj-sys.com/asn1tutorial/node10.html
91 define('FILE_ASN1_TYPE_NUMERIC_STRING', 18);
92 define('FILE_ASN1_TYPE_PRINTABLE_STRING',19);
93 define('FILE_ASN1_TYPE_TELETEX_STRING', 20); // T61String
94 define('FILE_ASN1_TYPE_VIDEOTEX_STRING', 21);
95 define('FILE_ASN1_TYPE_IA5_STRING', 22);
96 define('FILE_ASN1_TYPE_UTC_TIME', 23);
97 define('FILE_ASN1_TYPE_GENERALIZED_TIME',24);
98 define('FILE_ASN1_TYPE_GRAPHIC_STRING', 25);
99 define('FILE_ASN1_TYPE_VISIBLE_STRING', 26); // ISO646String
100 define('FILE_ASN1_TYPE_GENERAL_STRING', 27);
101 define('FILE_ASN1_TYPE_UNIVERSAL_STRING',28);
102 //define('FILE_ASN1_TYPE_CHARACTER_STRING',29);
103 define('FILE_ASN1_TYPE_BMP_STRING', 30);
109 * These tags are kinda place holders for other tags.
113 define('FILE_ASN1_TYPE_CHOICE', -1);
114 define('FILE_ASN1_TYPE_ANY', -2);
120 * Bypass normal encoding rules in File_ASN1::encodeDER()
122 * @author Jim Wigginton <terrafrost@php.net>
127 class File_ASN1_Element
{
139 * @param String $encoded
140 * @return File_ASN1_Element
143 function File_ASN1_Element($encoded)
145 $this->element
= $encoded;
150 * Pure-PHP ASN.1 Parser
152 * @author Jim Wigginton <terrafrost@php.net>
159 * ASN.1 object identifier
163 * @link http://en.wikipedia.org/wiki/Object_identifier
168 * Default date format
172 * @link http://php.net/class.datetime
174 var $format = 'D, d M y H:i:s O';
177 * Default date format
181 * @see File_ASN1::setTimeFormat()
182 * @see File_ASN1::asn1map()
183 * @link http://php.net/class.datetime
190 * If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as?
194 * @see File_ASN1::_encode_der()
199 * Type mapping table for the ANY type.
201 * Structured or unknown types are mapped to a FILE_ASN1_Element.
202 * Unambiguous types get the direct mapping (int/real/bool).
203 * Others are mapped as a choice, with an extra indexing level.
209 FILE_ASN1_TYPE_BOOLEAN
=> true,
210 FILE_ASN1_TYPE_INTEGER
=> true,
211 FILE_ASN1_TYPE_BIT_STRING
=> 'bitString',
212 FILE_ASN1_TYPE_OCTET_STRING
=> 'octetString',
213 FILE_ASN1_TYPE_NULL
=> 'null',
214 FILE_ASN1_TYPE_OBJECT_IDENTIFIER
=> 'objectIdentifier',
215 FILE_ASN1_TYPE_REAL
=> true,
216 FILE_ASN1_TYPE_ENUMERATED
=> 'enumerated',
217 FILE_ASN1_TYPE_UTF8_STRING
=> 'utf8String',
218 FILE_ASN1_TYPE_NUMERIC_STRING
=> 'numericString',
219 FILE_ASN1_TYPE_PRINTABLE_STRING
=> 'printableString',
220 FILE_ASN1_TYPE_TELETEX_STRING
=> 'teletexString',
221 FILE_ASN1_TYPE_VIDEOTEX_STRING
=> 'videotexString',
222 FILE_ASN1_TYPE_IA5_STRING
=> 'ia5String',
223 FILE_ASN1_TYPE_UTC_TIME
=> 'utcTime',
224 FILE_ASN1_TYPE_GENERALIZED_TIME
=> 'generalTime',
225 FILE_ASN1_TYPE_GRAPHIC_STRING
=> 'graphicString',
226 FILE_ASN1_TYPE_VISIBLE_STRING
=> 'visibleString',
227 FILE_ASN1_TYPE_GENERAL_STRING
=> 'generalString',
228 FILE_ASN1_TYPE_UNIVERSAL_STRING
=> 'universalString',
229 //FILE_ASN1_TYPE_CHARACTER_STRING => 'characterString',
230 FILE_ASN1_TYPE_BMP_STRING
=> 'bmpString'
234 * String type to character size mapping table.
236 * Non-convertable types are absent from this table.
237 * size == 0 indicates variable length encoding.
242 var $stringTypeSize = array(
243 FILE_ASN1_TYPE_UTF8_STRING
=> 0,
244 FILE_ASN1_TYPE_BMP_STRING
=> 2,
245 FILE_ASN1_TYPE_UNIVERSAL_STRING
=> 4,
246 FILE_ASN1_TYPE_PRINTABLE_STRING
=> 1,
247 FILE_ASN1_TYPE_TELETEX_STRING
=> 1,
248 FILE_ASN1_TYPE_IA5_STRING
=> 1,
249 FILE_ASN1_TYPE_VISIBLE_STRING
=> 1,
255 * Serves a similar purpose to openssl's asn1parse
257 * @param String $encoded
261 function decodeBER($encoded)
263 if (is_object($encoded) && strtolower(get_class($encoded)) == 'file_asn1_element') {
264 $encoded = $encoded->element
;
267 $this->encoded
= $encoded;
268 return $this->_decode_ber($encoded);
272 * Parse BER-encoding (Helper function)
274 * Sometimes we want to get the BER encoding of a particular tag. $start lets us do that without having to reencode.
275 * $encoded is passed by reference for the recursive calls done for FILE_ASN1_TYPE_BIT_STRING and
276 * FILE_ASN1_TYPE_OCTET_STRING. In those cases, the indefinite length is used.
278 * @param String $encoded
279 * @param Integer $start
283 function _decode_ber(&$encoded, $start = 0)
287 while ( strlen($encoded) ) {
288 $current = array('start' => $start);
290 $type = ord($this->_string_shift($encoded));
293 $constructed = ($type >> 5) & 1;
298 // process septets (since the eighth bit is ignored, it's not an octet)
300 $loop = ord($encoded[0]) >> 7;
302 $tag |
= ord($this->_string_shift($encoded)) & 0x7F;
307 // Length, as discussed in § 8.1.3 of X.690-0207.pdf#page=13
308 $length = ord($this->_string_shift($encoded));
310 if ( $length == 0x80 ) { // indefinite length
311 // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
312 // immediately available." -- § 8.1.3.2.c
313 //if ( !$constructed ) {
316 $length = strlen($encoded);
317 } elseif ( $length & 0x80 ) { // definite length, long form
318 // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
319 // support it up to four.
321 $temp = $this->_string_shift($encoded, $length);
323 extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT
), -4)));
326 // End-of-content, see §§ 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
327 if (!$type && !$length) {
330 $content = $this->_string_shift($encoded, $length);
332 /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
333 built-in types. It defines an application-independent data type that must be distinguishable from all other
334 data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
335 have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
336 a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
337 alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
338 data type; the term CONTEXT-SPECIFIC does not appear.
340 -- http://www.obj-sys.com/asn1tutorial/node12.html */
341 $class = ($type >> 6) & 3;
343 case FILE_ASN1_CLASS_APPLICATION
:
344 case FILE_ASN1_CLASS_PRIVATE
:
345 case FILE_ASN1_CLASS_CONTEXT_SPECIFIC
:
349 'content' => $constructed ?
$this->_decode_ber($content, $start) : $content,
350 'length' => $length +
$start - $current['start']
355 $current+
= array('type' => $tag);
357 // decode UNIVERSAL tags
359 case FILE_ASN1_TYPE_BOOLEAN
:
360 // "The contents octets shall consist of a single octet." -- § 8.2.1
361 //if (strlen($content) != 1) {
364 $current['content'] = (bool) ord($content[0]);
366 case FILE_ASN1_TYPE_INTEGER
:
367 case FILE_ASN1_TYPE_ENUMERATED
:
368 $current['content'] = new Math_BigInteger($content, -256);
370 case FILE_ASN1_TYPE_REAL
: // not currently supported
372 case FILE_ASN1_TYPE_BIT_STRING
:
373 // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
374 // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
377 $current['content'] = $content;
379 $temp = $this->_decode_ber($content, $start);
380 $length-= strlen($content);
381 $last = count($temp) - 1;
382 for ($i = 0; $i < $last; $i++
) {
383 // all subtags should be bit strings
384 //if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
387 $current['content'].= substr($temp[$i]['content'], 1);
389 // all subtags should be bit strings
390 //if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
393 $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
396 case FILE_ASN1_TYPE_OCTET_STRING
:
398 $current['content'] = $content;
400 $temp = $this->_decode_ber($content, $start);
401 $length-= strlen($content);
402 for ($i = 0, $size = count($temp); $i < $size; $i++
) {
403 // all subtags should be octet strings
404 //if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
407 $current['content'].= $temp[$i]['content'];
412 case FILE_ASN1_TYPE_NULL
:
413 // "The contents octets shall not contain any octets." -- § 8.8.2
414 //if (strlen($content)) {
418 case FILE_ASN1_TYPE_SEQUENCE
:
419 case FILE_ASN1_TYPE_SET
:
420 $current['content'] = $this->_decode_ber($content, $start);
422 case FILE_ASN1_TYPE_OBJECT_IDENTIFIER
:
423 $temp = ord($this->_string_shift($content));
424 $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp %
40);
427 while (strlen($content)) {
428 $temp = ord($this->_string_shift($content));
430 $valuen |
= $temp & 0x7F;
432 $current['content'].= ".$valuen";
436 // the eighth bit of the last byte should not be 1
441 /* Each character string type shall be encoded as if it had been declared:
442 [UNIVERSAL x] IMPLICIT OCTET STRING
444 -- X.690-0207.pdf#page=23 (§ 8.21.3)
446 Per that, we're not going to do any validation. If there are any illegal characters in the string,
447 we don't really care */
448 case FILE_ASN1_TYPE_NUMERIC_STRING
:
449 // 0,1,2,3,4,5,6,7,8,9, and space
450 case FILE_ASN1_TYPE_PRINTABLE_STRING
:
451 // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
452 // hyphen, full stop, solidus, colon, equal sign, question mark
453 case FILE_ASN1_TYPE_TELETEX_STRING
:
454 // The Teletex character set in CCITT's T61, space, and delete
455 // see http://en.wikipedia.org/wiki/Teletex#Character_sets
456 case FILE_ASN1_TYPE_VIDEOTEX_STRING
:
457 // The Videotex character set in CCITT's T.100 and T.101, space, and delete
458 case FILE_ASN1_TYPE_VISIBLE_STRING
:
459 // Printing character sets of international ASCII, and space
460 case FILE_ASN1_TYPE_IA5_STRING
:
461 // International Alphabet 5 (International ASCII)
462 case FILE_ASN1_TYPE_GRAPHIC_STRING
:
463 // All registered G sets, and space
464 case FILE_ASN1_TYPE_GENERAL_STRING
:
465 // All registered C and G sets, space and delete
466 case FILE_ASN1_TYPE_UTF8_STRING
:
468 case FILE_ASN1_TYPE_BMP_STRING
:
469 $current['content'] = $content;
471 case FILE_ASN1_TYPE_UTC_TIME
:
472 case FILE_ASN1_TYPE_GENERALIZED_TIME
:
473 $current['content'] = $this->_decodeTime($content, $tag);
479 $decoded[] = $current +
array('length' => $start - $current['start']);
488 * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
490 * @param Array $decoded
491 * @param Array $mapping
495 function asn1map($decoded, $mapping)
497 if (isset($mapping['explicit'])) {
498 $decoded = $decoded['content'][0];
502 case $mapping['type'] == FILE_ASN1_TYPE_ANY
:
503 $intype = $decoded['type'];
504 if (isset($decoded['constant']) ||
!isset($this->ANYmap
[$intype]) ||
($this->encoded
[$decoded['start']] & 0x20)) {
505 return new File_ASN1_Element(substr($this->encoded
, $decoded['start'], $decoded['length']));
507 $inmap = $this->ANYmap
[$intype];
508 if (is_string($inmap)) {
509 return array($inmap => $this->asn1map($decoded, array('type' => $intype) +
$mapping));
512 case $mapping['type'] == FILE_ASN1_TYPE_CHOICE
:
513 foreach ($mapping['children'] as $key => $option) {
515 case isset($option['constant']) && $option['constant'] == $decoded['constant']:
516 case !isset($option['constant']) && $option['type'] == $decoded['type']:
517 $value = $this->asn1map($decoded, $option);
520 return array($key => $value);
524 case isset($mapping['implicit']):
525 case isset($mapping['explicit']):
526 case $decoded['type'] == $mapping['type']:
532 if (isset($mapping['implicit'])) {
533 $decoded['type'] = $mapping['type'];
536 switch ($decoded['type']) {
537 case FILE_ASN1_TYPE_SEQUENCE
:
540 if (empty($decoded['content'])) {
544 // ignore the min and max
545 if (isset($mapping['min']) && isset($mapping['max'])) {
546 $child = $mapping['children'];
547 foreach ($decoded['content'] as $content) {
548 $map[] = $this->asn1map($content, $child);
553 $temp = $decoded['content'][$i = 0];
554 foreach ($mapping['children'] as $key => $child) {
555 if (!isset($child['optional']) && $child['type'] == FILE_ASN1_TYPE_CHOICE
) {
556 $map[$key] = $this->asn1map($temp, $child);
558 if (count($decoded['content']) == $i) {
561 $temp = $decoded['content'][$i];
565 $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL
;
567 if (isset($temp['constant'])) {
568 $tempClass = isset($temp['class']) ?
$temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC
;
570 if (isset($child['class'])) {
571 $childClass = $child['class'];
572 $constant = $child['cast'];
574 elseif (isset($child['constant'])) {
575 $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC
;
576 $constant = $child['constant'];
579 if (isset($child['optional'])) {
580 if (isset($constant) && isset($temp['constant'])) {
581 if (($constant == $temp['constant']) && ($childClass == $tempClass)) {
582 $map[$key] = $this->asn1map($temp, $child);
584 if (count($decoded['content']) == $i) {
587 $temp = $decoded['content'][$i];
589 } elseif (!isset($child['constant'])) {
590 // we could do this, as well:
591 // $buffer = $this->asn1map($temp, $child); if (isset($buffer)) { $map[$key] = $buffer; }
592 if ($child['type'] == $temp['type'] ||
$child['type'] == FILE_ASN1_TYPE_ANY
) {
593 $map[$key] = $this->asn1map($temp, $child);
595 if (count($decoded['content']) == $i) {
598 $temp = $decoded['content'][$i];
599 } elseif ($child['type'] == FILE_ASN1_TYPE_CHOICE
) {
600 $candidate = $this->asn1map($temp, $child);
601 if (!empty($candidate)) {
602 $map[$key] = $candidate;
604 if (count($decoded['content']) == $i) {
607 $temp = $decoded['content'][$i];
612 if (!isset($map[$key]) && isset($child['default'])) {
613 $map[$key] = $child['default'];
616 $map[$key] = $this->asn1map($temp, $child);
618 if (count($decoded['content']) == $i) {
621 $temp = $decoded['content'][$i];
626 // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
627 case FILE_ASN1_TYPE_SET
:
630 // ignore the min and max
631 if (isset($mapping['min']) && isset($mapping['max'])) {
632 $child = $mapping['children'];
633 foreach ($decoded['content'] as $content) {
634 $map[] = $this->asn1map($content, $child);
640 for ($i = 0; $i < count($decoded['content']); $i++
) {
641 foreach ($mapping['children'] as $key => $child) {
642 $temp = $decoded['content'][$i];
644 if (!isset($child['optional']) && $child['type'] == FILE_ASN1_TYPE_CHOICE
) {
645 $map[$key] = $this->asn1map($temp, $child);
649 $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL
;
651 if (isset($temp['constant'])) {
652 $tempClass = isset($temp['class']) ?
$temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC
;
654 if (isset($child['class'])) {
655 $childClass = $child['class'];
656 $constant = $child['cast'];
658 elseif (isset($child['constant'])) {
659 $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC
;
660 $constant = $child['constant'];
663 if (isset($constant) && isset($temp['constant'])) {
664 if (($constant == $temp['constant']) && ($childClass == $tempClass)) {
665 $map[$key] = $this->asn1map($temp['content'], $child);
667 } elseif (!isset($child['constant'])) {
668 // we could do this, as well:
669 // $buffer = $this->asn1map($temp['content'], $child); if (isset($buffer)) { $map[$key] = $buffer; }
670 if ($child['type'] == $temp['type']) {
671 $map[$key] = $this->asn1map($temp, $child);
677 foreach ($mapping['children'] as $key => $child) {
678 if (!isset($map[$key]) && isset($child['default'])) {
679 $map[$key] = $child['default'];
683 case FILE_ASN1_TYPE_OBJECT_IDENTIFIER
:
684 return isset($this->oids
[$decoded['content']]) ?
$this->oids
[$decoded['content']] : $decoded['content'];
685 case FILE_ASN1_TYPE_UTC_TIME
:
686 case FILE_ASN1_TYPE_GENERALIZED_TIME
:
687 if (isset($mapping['implicit'])) {
688 $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
690 return @date
($this->format
, $decoded['content']);
691 case FILE_ASN1_TYPE_BIT_STRING
:
692 if (isset($mapping['mapping'])) {
693 $offset = ord($decoded['content'][0]);
694 $size = (strlen($decoded['content']) - 1) * 8 - $offset;
696 From X.680-0207.pdf#page=46 (21.7):
698 "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
699 arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
700 therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
703 $bits = count($mapping['mapping']) == $size ?
array() : array_fill(0, count($mapping['mapping']) - $size, false);
704 for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
705 $current = ord($decoded['content'][$i]);
706 for ($j = $offset; $j < 8; $j++
) {
707 $bits[] = (bool) ($current & (1 << $j));
712 $map = array_reverse($mapping['mapping']);
713 foreach ($map as $i => $value) {
720 case FILE_ASN1_TYPE_OCTET_STRING
:
721 return base64_encode($decoded['content']);
722 case FILE_ASN1_TYPE_NULL
:
724 case FILE_ASN1_TYPE_BOOLEAN
:
725 return $decoded['content'];
726 case FILE_ASN1_TYPE_NUMERIC_STRING
:
727 case FILE_ASN1_TYPE_PRINTABLE_STRING
:
728 case FILE_ASN1_TYPE_TELETEX_STRING
:
729 case FILE_ASN1_TYPE_VIDEOTEX_STRING
:
730 case FILE_ASN1_TYPE_IA5_STRING
:
731 case FILE_ASN1_TYPE_GRAPHIC_STRING
:
732 case FILE_ASN1_TYPE_VISIBLE_STRING
:
733 case FILE_ASN1_TYPE_GENERAL_STRING
:
734 case FILE_ASN1_TYPE_UNIVERSAL_STRING
:
735 case FILE_ASN1_TYPE_UTF8_STRING
:
736 case FILE_ASN1_TYPE_BMP_STRING
:
737 return $decoded['content'];
738 case FILE_ASN1_TYPE_INTEGER
:
739 case FILE_ASN1_TYPE_ENUMERATED
:
740 $temp = $decoded['content'];
741 if (isset($mapping['implicit'])) {
742 $temp = new Math_BigInteger($decoded['content'], -256);
744 if (isset($mapping['mapping'])) {
745 $temp = (int) $temp->toString();
746 return isset($mapping['mapping'][$temp]) ?
747 $mapping['mapping'][$temp] :
757 * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
760 * @param String $source
761 * @param String $mapping
762 * @param Integer $idx
766 function encodeDER($source, $mapping)
768 $this->location
= array();
769 return $this->_encode_der($source, $mapping);
773 * ASN.1 Encode (Helper function)
775 * @param String $source
776 * @param String $mapping
777 * @param Integer $idx
781 function _encode_der($source, $mapping, $idx = NULL)
783 if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') {
784 return $source->element
;
787 // do not encode (implicitly optional) fields with value set to default
788 if (isset($mapping['default']) && $source === $mapping['default']) {
793 $this->location
[] = $idx;
796 $tag = $mapping['type'];
799 case FILE_ASN1_TYPE_SET
: // Children order is not important, thus process in sequence.
800 case FILE_ASN1_TYPE_SEQUENCE
:
801 $tag|
= 0x20; // set the constructed bit
804 // ignore the min and max
805 if (isset($mapping['min']) && isset($mapping['max'])) {
806 $child = $mapping['children'];
808 foreach ($source as $content) {
809 $temp = $this->_encode_der($content, $child);
810 if ($temp === false) {
818 foreach ($mapping['children'] as $key => $child) {
819 if (!isset($source[$key])) {
820 if (!isset($child['optional'])) {
826 $temp = $this->_encode_der($source[$key], $child, $key);
827 if ($temp === false) {
831 // An empty child encoding means it has been optimized out.
832 // Else we should have at least one tag byte.
837 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
838 if (isset($child['constant'])) {
840 From X.680-0207.pdf#page=58 (30.6):
842 "The tagging construction specifies explicit tagging if any of the following holds:
844 c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
845 AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
846 an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
848 if (isset($child['explicit']) ||
$child['type'] == FILE_ASN1_TYPE_CHOICE
) {
849 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC
<< 6) |
0x20 |
$child['constant']);
850 $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
852 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC
<< 6) |
(ord($temp[0]) & 0x20) |
$child['constant']);
853 $temp = $subtag . substr($temp, 1);
859 case FILE_ASN1_TYPE_CHOICE
:
862 foreach ($mapping['children'] as $key => $child) {
863 if (!isset($source[$key])) {
867 $temp = $this->_encode_der($source[$key], $child, $key);
868 if ($temp === false) {
872 // An empty child encoding means it has been optimized out.
873 // Else we should have at least one tag byte.
878 $tag = ord($temp[0]);
880 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
881 if (isset($child['constant'])) {
882 if (isset($child['explicit']) ||
$child['type'] == FILE_ASN1_TYPE_CHOICE
) {
883 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC
<< 6) |
0x20 |
$child['constant']);
884 $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
886 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC
<< 6) |
(ord($temp[0]) & 0x20) |
$child['constant']);
887 $temp = $subtag . substr($temp, 1);
893 array_pop($this->location
);
896 if ($temp && isset($mapping['cast'])) {
897 $temp[0] = chr(($mapping['class'] << 6) |
($tag & 0x20) |
$mapping['cast']);
901 case FILE_ASN1_TYPE_INTEGER
:
902 case FILE_ASN1_TYPE_ENUMERATED
:
903 if (!isset($mapping['mapping'])) {
904 $value = $source->toBytes(true);
906 $value = array_search($source, $mapping['mapping']);
907 if ($value === false) {
910 $value = new Math_BigInteger($value);
911 $value = $value->toBytes(true);
914 case FILE_ASN1_TYPE_UTC_TIME
:
915 case FILE_ASN1_TYPE_GENERALIZED_TIME
:
916 $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ?
'y' : 'Y';
918 $value = @gmdate
($format, strtotime($source)) . 'Z';
920 case FILE_ASN1_TYPE_BIT_STRING
:
921 if (isset($mapping['mapping'])) {
922 $bits = array_fill(0, count($mapping['mapping']), 0);
924 for ($i = 0; $i < count($mapping['mapping']); $i++
) {
925 if (in_array($mapping['mapping'][$i], $source)) {
931 $offset = 8 - (($size +
1) & 7);
932 $offset = $offset !== 8 ?
$offset : 0;
934 $value = chr($offset);
936 for ($i = $size +
1; $i < count($mapping['mapping']); $i++
) {
940 $bits = implode('', array_pad($bits, $size +
$offset +
1, 0));
941 $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
942 foreach ($bytes as $byte) {
943 $value.= chr(bindec($byte));
948 case FILE_ASN1_TYPE_OCTET_STRING
:
949 /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
950 the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
952 -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
953 $value = base64_decode($source);
955 case FILE_ASN1_TYPE_OBJECT_IDENTIFIER
:
956 $oid = preg_match('#(?:\d+\.)+#', $source) ?
$source : array_search($source, $this->oids
);
957 if ($oid === false) {
958 user_error('Invalid OID', E_USER_NOTICE
);
962 $parts = explode('.', $oid);
963 $value = chr(40 * $parts[0] +
$parts[1]);
964 for ($i = 2; $i < count($parts); $i++
) {
970 $temp = chr(0x80 |
($parts[$i] & 0x7F)) . $temp;
973 $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
978 case FILE_ASN1_TYPE_ANY
:
979 $loc = $this->location
;
981 array_pop($this->location
);
985 case !isset($source):
986 return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL
) +
$mapping);
987 case is_int($source):
988 case is_object($source) && strtolower(get_class($source)) == 'math_biginteger':
989 return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER
) +
$mapping);
990 case is_float($source):
991 return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL
) +
$mapping);
992 case is_bool($source):
993 return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN
) +
$mapping);
994 case is_array($source) && count($source) == 1:
995 $typename = implode('', array_keys($source));
996 $outtype = array_search($typename, $this->ANYmap
, true);
997 if ($outtype !== false) {
998 return $this->_encode_der($source[$typename], array('type' => $outtype) +
$mapping);
1002 $filters = $this->filters
;
1003 foreach ($loc as $part) {
1004 if (!isset($filters[$part])) {
1008 $filters = $filters[$part];
1010 if ($filters === false) {
1011 user_error('No filters defined for ' . implode('/', $loc), E_USER_NOTICE
);
1014 return $this->_encode_der($source, $filters +
$mapping);
1015 case FILE_ASN1_TYPE_NULL
:
1018 case FILE_ASN1_TYPE_NUMERIC_STRING
:
1019 case FILE_ASN1_TYPE_TELETEX_STRING
:
1020 case FILE_ASN1_TYPE_PRINTABLE_STRING
:
1021 case FILE_ASN1_TYPE_UNIVERSAL_STRING
:
1022 case FILE_ASN1_TYPE_UTF8_STRING
:
1023 case FILE_ASN1_TYPE_BMP_STRING
:
1024 case FILE_ASN1_TYPE_IA5_STRING
:
1025 case FILE_ASN1_TYPE_VISIBLE_STRING
:
1026 case FILE_ASN1_TYPE_VIDEOTEX_STRING
:
1027 case FILE_ASN1_TYPE_GRAPHIC_STRING
:
1028 case FILE_ASN1_TYPE_GENERAL_STRING
:
1031 case FILE_ASN1_TYPE_BOOLEAN
:
1032 $value = $source ?
"\xFF" : "\x00";
1035 user_error('Mapping provides no type definition for ' . implode('/', $this->location
), E_USER_NOTICE
);
1040 array_pop($this->location
);
1043 if (isset($mapping['cast'])) {
1044 $tag = ($mapping['class'] << 6) |
($tag & 0x20) |
$mapping['cast'];
1047 return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1051 * DER-encode the length
1053 * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
1054 * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information.
1057 * @param Integer $length
1060 function _encodeLength($length)
1062 if ($length <= 0x7F) {
1063 return chr($length);
1066 $temp = ltrim(pack('N', $length), chr(0));
1067 return pack('Ca*', 0x80 |
strlen($temp), $temp);
1071 * BER-decode the time
1073 * Called by _decode_ber() and in the case of implicit tags asn1map().
1076 * @param String $content
1077 * @param Integer $tag
1080 function _decodeTime($content, $tag)
1083 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1084 http://www.obj-sys.com/asn1tutorial/node15.html
1087 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1088 http://www.obj-sys.com/asn1tutorial/node14.html */
1090 $pattern = $tag == FILE_ASN1_TYPE_UTC_TIME ?
1091 '#(..)(..)(..)(..)(..)(..)(.*)#' :
1092 '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';
1094 preg_match($pattern, $content, $matches);
1096 list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;
1098 if ($tag == FILE_ASN1_TYPE_UTC_TIME
) {
1099 $year = $year >= 50 ?
"19$year" : "20$year";
1102 if ($timezone == 'Z') {
1103 $mktime = 'gmmktime';
1105 } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
1106 $mktime = 'gmmktime';
1107 $timezone = 60 * $matches[3] +
3600 * $matches[2];
1108 if ($matches[1] == '-') {
1109 $timezone = -$timezone;
1116 return @$mktime($hour, $minute, $second, $month, $day, $year) +
$timezone;
1120 * Set the time format
1122 * Sets the time / date format for asn1map().
1125 * @param String $format
1127 function setTimeFormat($format)
1129 $this->format
= $format;
1135 * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1138 * @param Array $oids
1140 function loadOIDs($oids)
1142 $this->oids
= $oids;
1148 * See File_X509, etc, for an example.
1151 * @param Array $filters
1153 function loadFilters($filters)
1155 $this->filters
= $filters;
1161 * Inspired by array_shift
1163 * @param String $string
1164 * @param optional Integer $index
1168 function _string_shift(&$string, $index = 1)
1170 $substr = substr($string, 0, $index);
1171 $string = substr($string, $index);
1176 * String type conversion
1178 * This is a lazy conversion, dealing only with character size.
1179 * No real conversion table is used.
1182 * @param optional Integer $from
1183 * @param optional Integer $to
1187 function convert($in, $from = FILE_ASN1_TYPE_UTF8_STRING
, $to = FILE_ASN1_TYPE_UTF8_STRING
)
1189 if (!isset($this->stringTypeSize
[$from]) ||
!isset($this->stringTypeSize
[$to])) {
1192 $insize = $this->stringTypeSize
[$from];
1193 $outsize = $this->stringTypeSize
[$to];
1194 $inlength = strlen($in);
1197 for ($i = 0; $i < $inlength;) {
1198 if ($inlength - $i < $insize) {
1202 // Get an input character as a 32-bit value.
1203 $c = ord($in[$i++
]);
1206 $c = ($c << 8) |
ord($in[$i++
]);
1207 $c = ($c << 8) |
ord($in[$i++
]);
1209 $c = ($c << 8) |
ord($in[$i++
]);
1212 case ($c & 0x80) == 0x00:
1214 case ($c & 0x40) == 0x00:
1219 if ($bit > 25 ||
$i >= $inlength ||
(ord($in[$i]) & 0xC0) != 0x80) {
1222 $c = ($c << 6) |
(ord($in[$i++
]) & 0x3F);
1225 } while ($c & $bit);
1230 // Convert and append the character to output string.
1234 $v .= chr($c & 0xFF);
1236 $v .= chr($c & 0xFF);
1239 $v .= chr($c & 0xFF);
1242 $v .= chr($c & 0xFF);
1248 case ($c & 0x80000000) != 0:
1250 case $c >= 0x04000000:
1251 $v .= chr(0x80 |
($c & 0x3F));
1252 $c = ($c >> 6) |
0x04000000;
1253 case $c >= 0x00200000:
1254 $v .= chr(0x80 |
($c & 0x3F));
1255 $c = ($c >> 6) |
0x00200000;
1256 case $c >= 0x00010000:
1257 $v .= chr(0x80 |
($c & 0x3F));
1258 $c = ($c >> 6) |
0x00010000;
1259 case $c >= 0x00000800:
1260 $v .= chr(0x80 |
($c & 0x3F));
1261 $c = ($c >> 6) |
0x00000800;
1262 case $c >= 0x00000080:
1263 $v .= chr(0x80 |
($c & 0x3F));
1264 $c = ($c >> 6) |
0x000000C0;