PHPSECLIB 0.3.1 added to the project to support SFTP transfers of lab orders and...
[openemr.git] / library / phpseclib / File / ASN1.php
blob0ed044fde1405fb48002903d70c00a55b9bb1031
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 /**
5 * Pure-PHP ASN.1 Parser
7 * PHP versions 4 and 5
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
11 * DER blobs.
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
33 * THE SOFTWARE.
35 * @category File
36 * @package File_ASN1
37 * @author Jim Wigginton <terrafrost@php.net>
38 * @copyright MMXII Jim Wigginton
39 * @license http://www.opensource.org/licenses/mit-license.html MIT License
40 * @version $Id$
41 * @link http://phpseclib.sourceforge.net
44 /**
45 * Include Math_BigInteger
47 if (!class_exists('Math_BigInteger')) {
48 require_once('Math/BigInteger.php');
51 /**#@+
52 * Tag Classes
54 * @access private
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);
61 /**#@-*/
63 /**#@+
64 * Tag Classes
66 * @access private
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
84 /**#@-*/
85 /**#@+
86 * More Tag Classes
88 * @access private
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);
104 /**#@-*/
106 /**#@+
107 * Tag Aliases
109 * These tags are kinda place holders for other tags.
111 * @access private
113 define('FILE_ASN1_TYPE_CHOICE', -1);
114 define('FILE_ASN1_TYPE_ANY', -2);
115 /**#@-*/
118 * ASN.1 Element
120 * Bypass normal encoding rules in File_ASN1::encodeDER()
122 * @author Jim Wigginton <terrafrost@php.net>
123 * @version 0.3.0
124 * @access public
125 * @package File_ASN1
127 class File_ASN1_Element {
129 * Raw element value
131 * @var String
132 * @access private
134 var $element;
137 * Constructor
139 * @param String $encoded
140 * @return File_ASN1_Element
141 * @access public
143 function File_ASN1_Element($encoded)
145 $this->element = $encoded;
150 * Pure-PHP ASN.1 Parser
152 * @author Jim Wigginton <terrafrost@php.net>
153 * @version 0.3.0
154 * @access public
155 * @package File_ASN1
157 class File_ASN1 {
159 * ASN.1 object identifier
161 * @var Array
162 * @access private
163 * @link http://en.wikipedia.org/wiki/Object_identifier
165 var $oids = array();
168 * Default date format
170 * @var String
171 * @access private
172 * @link http://php.net/class.datetime
174 var $format = 'D, d M y H:i:s O';
177 * Default date format
179 * @var Array
180 * @access private
181 * @see File_ASN1::setTimeFormat()
182 * @see File_ASN1::asn1map()
183 * @link http://php.net/class.datetime
185 var $encoded;
188 * Filters
190 * If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as?
192 * @var Array
193 * @access private
194 * @see File_ASN1::_encode_der()
196 var $filters;
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.
205 * @var Array
206 * @access public
208 var $ANYmap = array(
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.
239 * @var Array
240 * @access public
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,
253 * Parse BER-encoding
255 * Serves a similar purpose to openssl's asn1parse
257 * @param String $encoded
258 * @return Array
259 * @access public
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
280 * @return Array
281 * @access private
283 function _decode_ber(&$encoded, $start = 0)
285 $decoded = array();
287 while ( strlen($encoded) ) {
288 $current = array('start' => $start);
290 $type = ord($this->_string_shift($encoded));
291 $start++;
293 $constructed = ($type >> 5) & 1;
295 $tag = $type & 0x1F;
296 if ($tag == 0x1F) {
297 $tag = 0;
298 // process septets (since the eighth bit is ignored, it's not an octet)
299 do {
300 $loop = ord($encoded[0]) >> 7;
301 $tag <<= 7;
302 $tag |= ord($this->_string_shift($encoded)) & 0x7F;
303 $start++;
304 } while ( $loop );
307 // Length, as discussed in § 8.1.3 of X.690-0207.pdf#page=13
308 $length = ord($this->_string_shift($encoded));
309 $start++;
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 ) {
314 // return false;
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.
320 $length&= 0x7F;
321 $temp = $this->_string_shift($encoded, $length);
322 $start+= $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) {
328 return $decoded;
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;
342 switch ($class) {
343 case FILE_ASN1_CLASS_APPLICATION:
344 case FILE_ASN1_CLASS_PRIVATE:
345 case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
346 $decoded[] = array(
347 'type' => $class,
348 'constant' => $tag,
349 'content' => $constructed ? $this->_decode_ber($content, $start) : $content,
350 'length' => $length + $start - $current['start']
351 ) + $current;
352 continue 2;
355 $current+= array('type' => $tag);
357 // decode UNIVERSAL tags
358 switch ($tag) {
359 case FILE_ASN1_TYPE_BOOLEAN:
360 // "The contents octets shall consist of a single octet." -- § 8.2.1
361 //if (strlen($content) != 1) {
362 // return false;
364 $current['content'] = (bool) ord($content[0]);
365 break;
366 case FILE_ASN1_TYPE_INTEGER:
367 case FILE_ASN1_TYPE_ENUMERATED:
368 $current['content'] = new Math_BigInteger($content, -256);
369 break;
370 case FILE_ASN1_TYPE_REAL: // not currently supported
371 return false;
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
375 // seven.
376 if (!$constructed) {
377 $current['content'] = $content;
378 } else {
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) {
385 // return false;
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) {
391 // return false;
393 $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
395 break;
396 case FILE_ASN1_TYPE_OCTET_STRING:
397 if (!$constructed) {
398 $current['content'] = $content;
399 } else {
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) {
405 // return false;
407 $current['content'].= $temp[$i]['content'];
409 // $length =
411 break;
412 case FILE_ASN1_TYPE_NULL:
413 // "The contents octets shall not contain any octets." -- § 8.8.2
414 //if (strlen($content)) {
415 // return false;
417 break;
418 case FILE_ASN1_TYPE_SEQUENCE:
419 case FILE_ASN1_TYPE_SET:
420 $current['content'] = $this->_decode_ber($content, $start);
421 break;
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);
425 $valuen = 0;
426 // process septets
427 while (strlen($content)) {
428 $temp = ord($this->_string_shift($content));
429 $valuen <<= 7;
430 $valuen |= $temp & 0x7F;
431 if (~$temp & 0x80) {
432 $current['content'].= ".$valuen";
433 $valuen = 0;
436 // the eighth bit of the last byte should not be 1
437 //if ($temp >> 7) {
438 // return false;
440 break;
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:
467 // ????
468 case FILE_ASN1_TYPE_BMP_STRING:
469 $current['content'] = $content;
470 break;
471 case FILE_ASN1_TYPE_UTC_TIME:
472 case FILE_ASN1_TYPE_GENERALIZED_TIME:
473 $current['content'] = $this->_decodeTime($content, $tag);
474 default:
478 $start+= $length;
479 $decoded[] = $current + array('length' => $start - $current['start']);
482 return $decoded;
486 * ASN.1 Decode
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
492 * @return Array
493 * @access public
495 function asn1map($decoded, $mapping)
497 if (isset($mapping['explicit'])) {
498 $decoded = $decoded['content'][0];
501 switch (true) {
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));
511 break;
512 case $mapping['type'] == FILE_ASN1_TYPE_CHOICE:
513 foreach ($mapping['children'] as $key => $option) {
514 switch (true) {
515 case isset($option['constant']) && $option['constant'] == $decoded['constant']:
516 case !isset($option['constant']) && $option['type'] == $decoded['type']:
517 $value = $this->asn1map($decoded, $option);
519 if (isset($value)) {
520 return array($key => $value);
523 return NULL;
524 case isset($mapping['implicit']):
525 case isset($mapping['explicit']):
526 case $decoded['type'] == $mapping['type']:
527 break;
528 default:
529 return NULL;
532 if (isset($mapping['implicit'])) {
533 $decoded['type'] = $mapping['type'];
536 switch ($decoded['type']) {
537 case FILE_ASN1_TYPE_SEQUENCE:
538 $map = array();
540 if (empty($decoded['content'])) {
541 return $map;
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);
550 return $map;
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);
557 $i++;
558 if (count($decoded['content']) == $i) {
559 break;
561 $temp = $decoded['content'][$i];
562 continue;
565 $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
566 $constant = NULL;
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);
583 $i++;
584 if (count($decoded['content']) == $i) {
585 break;
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);
594 $i++;
595 if (count($decoded['content']) == $i) {
596 break;
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;
603 $i++;
604 if (count($decoded['content']) == $i) {
605 break;
607 $temp = $decoded['content'][$i];
612 if (!isset($map[$key]) && isset($child['default'])) {
613 $map[$key] = $child['default'];
615 } else {
616 $map[$key] = $this->asn1map($temp, $child);
617 $i++;
618 if (count($decoded['content']) == $i) {
619 break;
621 $temp = $decoded['content'][$i];
625 return $map;
626 // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
627 case FILE_ASN1_TYPE_SET:
628 $map = array();
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);
637 return $map;
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);
646 continue;
649 $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
650 $constant = NULL;
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'];
682 return $map;
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
701 0 bits."
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));
709 $offset = 0;
711 $values = array();
712 $map = array_reverse($mapping['mapping']);
713 foreach ($map as $i => $value) {
714 if ($bits[$i]) {
715 $values[] = $value;
718 return $values;
720 case FILE_ASN1_TYPE_OCTET_STRING:
721 return base64_encode($decoded['content']);
722 case FILE_ASN1_TYPE_NULL:
723 return '';
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] :
748 false;
750 return $temp;
755 * ASN.1 Encode
757 * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
758 * an ASN.1 compiler.
760 * @param String $source
761 * @param String $mapping
762 * @param Integer $idx
763 * @return String
764 * @access public
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
778 * @return String
779 * @access private
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']) {
789 return '';
792 if (isset($idx)) {
793 $this->location[] = $idx;
796 $tag = $mapping['type'];
798 switch ($tag) {
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
802 $value = '';
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) {
811 return false;
813 $value.= $temp;
815 break;
818 foreach ($mapping['children'] as $key => $child) {
819 if (!isset($source[$key])) {
820 if (!isset($child['optional'])) {
821 return false;
823 continue;
826 $temp = $this->_encode_der($source[$key], $child, $key);
827 if ($temp === false) {
828 return false;
831 // An empty child encoding means it has been optimized out.
832 // Else we should have at least one tag byte.
833 if ($temp === '') {
834 continue;
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;
851 } else {
852 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
853 $temp = $subtag . substr($temp, 1);
856 $value.= $temp;
858 break;
859 case FILE_ASN1_TYPE_CHOICE:
860 $temp = false;
862 foreach ($mapping['children'] as $key => $child) {
863 if (!isset($source[$key])) {
864 continue;
867 $temp = $this->_encode_der($source[$key], $child, $key);
868 if ($temp === false) {
869 return false;
872 // An empty child encoding means it has been optimized out.
873 // Else we should have at least one tag byte.
874 if ($temp === '') {
875 continue;
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;
885 } else {
886 $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
887 $temp = $subtag . substr($temp, 1);
892 if (isset($idx)) {
893 array_pop($this->location);
896 if ($temp && isset($mapping['cast'])) {
897 $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
900 return $temp;
901 case FILE_ASN1_TYPE_INTEGER:
902 case FILE_ASN1_TYPE_ENUMERATED:
903 if (!isset($mapping['mapping'])) {
904 $value = $source->toBytes(true);
905 } else {
906 $value = array_search($source, $mapping['mapping']);
907 if ($value === false) {
908 return false;
910 $value = new Math_BigInteger($value);
911 $value = $value->toBytes(true);
913 break;
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';
917 $format.= 'mdHis';
918 $value = @gmdate($format, strtotime($source)) . 'Z';
919 break;
920 case FILE_ASN1_TYPE_BIT_STRING:
921 if (isset($mapping['mapping'])) {
922 $bits = array_fill(0, count($mapping['mapping']), 0);
923 $size = 0;
924 for ($i = 0; $i < count($mapping['mapping']); $i++) {
925 if (in_array($mapping['mapping'][$i], $source)) {
926 $bits[$i] = 1;
927 $size = $i;
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++) {
937 unset($bits[$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));
946 break;
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);
954 break;
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);
959 return false;
961 $value = '';
962 $parts = explode('.', $oid);
963 $value = chr(40 * $parts[0] + $parts[1]);
964 for ($i = 2; $i < count($parts); $i++) {
965 $temp = '';
966 if (!$parts[$i]) {
967 $temp = "\0";
968 } else {
969 while ($parts[$i]) {
970 $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
971 $parts[$i] >>= 7;
973 $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
975 $value.= $temp;
977 break;
978 case FILE_ASN1_TYPE_ANY:
979 $loc = $this->location;
980 if (isset($idx)) {
981 array_pop($this->location);
984 switch (true) {
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])) {
1005 $filters = false;
1006 break;
1008 $filters = $filters[$part];
1010 if ($filters === false) {
1011 user_error('No filters defined for ' . implode('/', $loc), E_USER_NOTICE);
1012 return false;
1014 return $this->_encode_der($source, $filters + $mapping);
1015 case FILE_ASN1_TYPE_NULL:
1016 $value = '';
1017 break;
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:
1029 $value = $source;
1030 break;
1031 case FILE_ASN1_TYPE_BOOLEAN:
1032 $value = $source ? "\xFF" : "\x00";
1033 break;
1034 default:
1035 user_error('Mapping provides no type definition for ' . implode('/', $this->location), E_USER_NOTICE);
1036 return false;
1039 if (isset($idx)) {
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.
1056 * @access private
1057 * @param Integer $length
1058 * @return String
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().
1075 * @access private
1076 * @param String $content
1077 * @param Integer $tag
1078 * @return String
1080 function _decodeTime($content, $tag)
1082 /* UTCTime:
1083 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1084 http://www.obj-sys.com/asn1tutorial/node15.html
1086 GeneralizedTime:
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';
1104 $timezone = 0;
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;
1111 } else {
1112 $mktime = 'mktime';
1113 $timezone = 0;
1116 return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
1120 * Set the time format
1122 * Sets the time / date format for asn1map().
1124 * @access public
1125 * @param String $format
1127 function setTimeFormat($format)
1129 $this->format = $format;
1133 * Load OIDs
1135 * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1137 * @access public
1138 * @param Array $oids
1140 function loadOIDs($oids)
1142 $this->oids = $oids;
1146 * Load filters
1148 * See File_X509, etc, for an example.
1150 * @access public
1151 * @param Array $filters
1153 function loadFilters($filters)
1155 $this->filters = $filters;
1159 * String Shift
1161 * Inspired by array_shift
1163 * @param String $string
1164 * @param optional Integer $index
1165 * @return String
1166 * @access private
1168 function _string_shift(&$string, $index = 1)
1170 $substr = substr($string, 0, $index);
1171 $string = substr($string, $index);
1172 return $substr;
1176 * String type conversion
1178 * This is a lazy conversion, dealing only with character size.
1179 * No real conversion table is used.
1181 * @param String $in
1182 * @param optional Integer $from
1183 * @param optional Integer $to
1184 * @return String
1185 * @access public
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])) {
1190 return false;
1192 $insize = $this->stringTypeSize[$from];
1193 $outsize = $this->stringTypeSize[$to];
1194 $inlength = strlen($in);
1195 $out = '';
1197 for ($i = 0; $i < $inlength;) {
1198 if ($inlength - $i < $insize) {
1199 return false;
1202 // Get an input character as a 32-bit value.
1203 $c = ord($in[$i++]);
1204 switch (true) {
1205 case $insize == 4:
1206 $c = ($c << 8) | ord($in[$i++]);
1207 $c = ($c << 8) | ord($in[$i++]);
1208 case $insize == 2:
1209 $c = ($c << 8) | ord($in[$i++]);
1210 case $insize == 1:
1211 break;
1212 case ($c & 0x80) == 0x00:
1213 break;
1214 case ($c & 0x40) == 0x00:
1215 return false;
1216 default:
1217 $bit = 6;
1218 do {
1219 if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1220 return false;
1222 $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1223 $bit += 5;
1224 $mask = 1 << $bit;
1225 } while ($c & $bit);
1226 $c &= $mask - 1;
1227 break;
1230 // Convert and append the character to output string.
1231 $v = '';
1232 switch (true) {
1233 case $outsize == 4:
1234 $v .= chr($c & 0xFF);
1235 $c >>= 8;
1236 $v .= chr($c & 0xFF);
1237 $c >>= 8;
1238 case $outsize == 2:
1239 $v .= chr($c & 0xFF);
1240 $c >>= 8;
1241 case $outsize == 1:
1242 $v .= chr($c & 0xFF);
1243 $c >>= 8;
1244 if ($c) {
1245 return false;
1247 break;
1248 case ($c & 0x80000000) != 0:
1249 return false;
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;
1265 default:
1266 $v .= chr($c);
1267 break;
1269 $out .= strrev($v);
1271 return $out;