]> git.mxchange.org Git - friendica-addons.git/blob - securemail/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php
1d66793a13000ab75b353b21f9b053ed001d3362
[friendica-addons.git] / securemail / vendor / phpseclib / phpseclib / phpseclib / File / ASN1.php
1 <?php
2
3 /**
4  * Pure-PHP ASN.1 Parser
5  *
6  * PHP versions 4 and 5
7  *
8  * ASN.1 provides the semantics for data encoded using various schemes.  The most commonly
9  * utilized scheme is DER or the "Distinguished Encoding Rules".  PEM's are base64 encoded
10  * DER blobs.
11  *
12  * File_ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
13  *
14  * Uses the 1988 ASN.1 syntax.
15  *
16  * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
17  * of this software and associated documentation files (the "Software"), to deal
18  * in the Software without restriction, including without limitation the rights
19  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20  * copies of the Software, and to permit persons to whom the Software is
21  * furnished to do so, subject to the following conditions:
22  *
23  * The above copyright notice and this permission notice shall be included in
24  * all copies or substantial portions of the Software.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32  * THE SOFTWARE.
33  *
34  * @category  File
35  * @package   File_ASN1
36  * @author    Jim Wigginton <terrafrost@php.net>
37  * @copyright 2012 Jim Wigginton
38  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
39  * @link      http://phpseclib.sourceforge.net
40  */
41
42 /**#@+
43  * Tag Classes
44  *
45  * @access private
46  * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
47  */
48 define('FILE_ASN1_CLASS_UNIVERSAL',        0);
49 define('FILE_ASN1_CLASS_APPLICATION',      1);
50 define('FILE_ASN1_CLASS_CONTEXT_SPECIFIC', 2);
51 define('FILE_ASN1_CLASS_PRIVATE',          3);
52 /**#@-*/
53
54 /**#@+
55  * Tag Classes
56  *
57  * @access private
58  * @link http://www.obj-sys.com/asn1tutorial/node124.html
59  */
60 define('FILE_ASN1_TYPE_BOOLEAN',           1);
61 define('FILE_ASN1_TYPE_INTEGER',           2);
62 define('FILE_ASN1_TYPE_BIT_STRING',        3);
63 define('FILE_ASN1_TYPE_OCTET_STRING',      4);
64 define('FILE_ASN1_TYPE_NULL',              5);
65 define('FILE_ASN1_TYPE_OBJECT_IDENTIFIER', 6);
66 //define('FILE_ASN1_TYPE_OBJECT_DESCRIPTOR', 7);
67 //define('FILE_ASN1_TYPE_INSTANCE_OF',       8); // EXTERNAL
68 define('FILE_ASN1_TYPE_REAL',              9);
69 define('FILE_ASN1_TYPE_ENUMERATED',       10);
70 //define('FILE_ASN1_TYPE_EMBEDDED',         11);
71 define('FILE_ASN1_TYPE_UTF8_STRING',      12);
72 //define('FILE_ASN1_TYPE_RELATIVE_OID',     13);
73 define('FILE_ASN1_TYPE_SEQUENCE',         16); // SEQUENCE OF
74 define('FILE_ASN1_TYPE_SET',              17); // SET OF
75 /**#@-*/
76 /**#@+
77  * More Tag Classes
78  *
79  * @access private
80  * @link http://www.obj-sys.com/asn1tutorial/node10.html
81  */
82 define('FILE_ASN1_TYPE_NUMERIC_STRING',   18);
83 define('FILE_ASN1_TYPE_PRINTABLE_STRING', 19);
84 define('FILE_ASN1_TYPE_TELETEX_STRING',   20); // T61String
85 define('FILE_ASN1_TYPE_VIDEOTEX_STRING',  21);
86 define('FILE_ASN1_TYPE_IA5_STRING',       22);
87 define('FILE_ASN1_TYPE_UTC_TIME',         23);
88 define('FILE_ASN1_TYPE_GENERALIZED_TIME', 24);
89 define('FILE_ASN1_TYPE_GRAPHIC_STRING',   25);
90 define('FILE_ASN1_TYPE_VISIBLE_STRING',   26); // ISO646String
91 define('FILE_ASN1_TYPE_GENERAL_STRING',   27);
92 define('FILE_ASN1_TYPE_UNIVERSAL_STRING', 28);
93 //define('FILE_ASN1_TYPE_CHARACTER_STRING', 29);
94 define('FILE_ASN1_TYPE_BMP_STRING',       30);
95 /**#@-*/
96
97 /**#@+
98  * Tag Aliases
99  *
100  * These tags are kinda place holders for other tags.
101  *
102  * @access private
103  */
104 define('FILE_ASN1_TYPE_CHOICE',          -1);
105 define('FILE_ASN1_TYPE_ANY',             -2);
106 /**#@-*/
107
108 /**
109  * ASN.1 Element
110  *
111  * Bypass normal encoding rules in File_ASN1::encodeDER()
112  *
113  * @package File_ASN1
114  * @author  Jim Wigginton <terrafrost@php.net>
115  * @access  public
116  */
117 class File_ASN1_Element
118 {
119     /**
120      * Raw element value
121      *
122      * @var String
123      * @access private
124      */
125     var $element;
126
127     /**
128      * Constructor
129      *
130      * @param String $encoded
131      * @return File_ASN1_Element
132      * @access public
133      */
134     function File_ASN1_Element($encoded)
135     {
136         $this->element = $encoded;
137     }
138 }
139
140 /**
141  * Pure-PHP ASN.1 Parser
142  *
143  * @package File_ASN1
144  * @author  Jim Wigginton <terrafrost@php.net>
145  * @access  public
146  */
147 class File_ASN1
148 {
149     /**
150      * ASN.1 object identifier
151      *
152      * @var Array
153      * @access private
154      * @link http://en.wikipedia.org/wiki/Object_identifier
155      */
156     var $oids = array();
157
158     /**
159      * Default date format
160      *
161      * @var String
162      * @access private
163      * @link http://php.net/class.datetime
164      */
165     var $format = 'D, d M Y H:i:s O';
166
167     /**
168      * Default date format
169      *
170      * @var Array
171      * @access private
172      * @see File_ASN1::setTimeFormat()
173      * @see File_ASN1::asn1map()
174      * @link http://php.net/class.datetime
175      */
176     var $encoded;
177
178     /**
179      * Filters
180      *
181      * If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as?
182      *
183      * @var Array
184      * @access private
185      * @see File_ASN1::_encode_der()
186      */
187     var $filters;
188
189     /**
190      * Type mapping table for the ANY type.
191      *
192      * Structured or unknown types are mapped to a FILE_ASN1_Element.
193      * Unambiguous types get the direct mapping (int/real/bool).
194      * Others are mapped as a choice, with an extra indexing level.
195      *
196      * @var Array
197      * @access public
198      */
199     var $ANYmap = array(
200         FILE_ASN1_TYPE_BOOLEAN              => true,
201         FILE_ASN1_TYPE_INTEGER              => true,
202         FILE_ASN1_TYPE_BIT_STRING           => 'bitString',
203         FILE_ASN1_TYPE_OCTET_STRING         => 'octetString',
204         FILE_ASN1_TYPE_NULL                 => 'null',
205         FILE_ASN1_TYPE_OBJECT_IDENTIFIER    => 'objectIdentifier',
206         FILE_ASN1_TYPE_REAL                 => true,
207         FILE_ASN1_TYPE_ENUMERATED           => 'enumerated',
208         FILE_ASN1_TYPE_UTF8_STRING          => 'utf8String',
209         FILE_ASN1_TYPE_NUMERIC_STRING       => 'numericString',
210         FILE_ASN1_TYPE_PRINTABLE_STRING     => 'printableString',
211         FILE_ASN1_TYPE_TELETEX_STRING       => 'teletexString',
212         FILE_ASN1_TYPE_VIDEOTEX_STRING      => 'videotexString',
213         FILE_ASN1_TYPE_IA5_STRING           => 'ia5String',
214         FILE_ASN1_TYPE_UTC_TIME             => 'utcTime',
215         FILE_ASN1_TYPE_GENERALIZED_TIME     => 'generalTime',
216         FILE_ASN1_TYPE_GRAPHIC_STRING       => 'graphicString',
217         FILE_ASN1_TYPE_VISIBLE_STRING       => 'visibleString',
218         FILE_ASN1_TYPE_GENERAL_STRING       => 'generalString',
219         FILE_ASN1_TYPE_UNIVERSAL_STRING     => 'universalString',
220         //FILE_ASN1_TYPE_CHARACTER_STRING     => 'characterString',
221         FILE_ASN1_TYPE_BMP_STRING           => 'bmpString'
222     );
223
224     /**
225      * String type to character size mapping table.
226      *
227      * Non-convertable types are absent from this table.
228      * size == 0 indicates variable length encoding.
229      *
230      * @var Array
231      * @access public
232      */
233     var $stringTypeSize = array(
234         FILE_ASN1_TYPE_UTF8_STRING      => 0,
235         FILE_ASN1_TYPE_BMP_STRING       => 2,
236         FILE_ASN1_TYPE_UNIVERSAL_STRING => 4,
237         FILE_ASN1_TYPE_PRINTABLE_STRING => 1,
238         FILE_ASN1_TYPE_TELETEX_STRING   => 1,
239         FILE_ASN1_TYPE_IA5_STRING       => 1,
240         FILE_ASN1_TYPE_VISIBLE_STRING   => 1,
241     );
242
243     /**
244      * Default Constructor.
245      *
246      * @access public
247      */
248     function File_ASN1()
249     {
250         static $static_init = null;
251         if (!$static_init) {
252             $static_init = true;
253             if (!class_exists('Math_BigInteger')) {
254                 include_once 'Math/BigInteger.php';
255             }
256         }
257     }
258
259     /**
260      * Parse BER-encoding
261      *
262      * Serves a similar purpose to openssl's asn1parse
263      *
264      * @param String $encoded
265      * @return Array
266      * @access public
267      */
268     function decodeBER($encoded)
269     {
270         if (is_object($encoded) && strtolower(get_class($encoded)) == 'file_asn1_element') {
271             $encoded = $encoded->element;
272         }
273
274         $this->encoded = $encoded;
275         // encapsulate in an array for BC with the old decodeBER
276         return array($this->_decode_ber($encoded));
277     }
278
279     /**
280      * Parse BER-encoding (Helper function)
281      *
282      * Sometimes we want to get the BER encoding of a particular tag.  $start lets us do that without having to reencode.
283      * $encoded is passed by reference for the recursive calls done for FILE_ASN1_TYPE_BIT_STRING and
284      * FILE_ASN1_TYPE_OCTET_STRING. In those cases, the indefinite length is used.
285      *
286      * @param String $encoded
287      * @param Integer $start
288      * @return Array
289      * @access private
290      */
291     function _decode_ber($encoded, $start = 0)
292     {
293         $current = array('start' => $start);
294
295         $type = ord($this->_string_shift($encoded));
296         $start++;
297
298         $constructed = ($type >> 5) & 1;
299
300         $tag = $type & 0x1F;
301         if ($tag == 0x1F) {
302             $tag = 0;
303             // process septets (since the eighth bit is ignored, it's not an octet)
304             do {
305                 $loop = ord($encoded[0]) >> 7;
306                 $tag <<= 7;
307                 $tag |= ord($this->_string_shift($encoded)) & 0x7F;
308                 $start++;
309             } while ( $loop );
310         }
311
312         // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
313         $length = ord($this->_string_shift($encoded));
314         $start++;
315         if ( $length == 0x80 ) { // indefinite length
316             // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
317             //  immediately available." -- paragraph 8.1.3.2.c
318             $length = strlen($encoded);
319         } elseif ( $length & 0x80 ) { // definite length, long form
320             // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
321             // support it up to four.
322             $length&= 0x7F;
323             $temp = $this->_string_shift($encoded, $length);
324             // tags of indefinte length don't really have a header length; this length includes the tag
325             $current+= array('headerlength' => $length + 2);
326             $start+= $length;
327             extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
328         } else {
329             $current+= array('headerlength' => 2);
330         }
331
332         $content = $this->_string_shift($encoded, $length);
333
334         // at this point $length can be overwritten. it's only accurate for definite length things as is
335
336         /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
337            built-in types. It defines an application-independent data type that must be distinguishable from all other
338            data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
339            have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
340            a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
341            alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
342            data type; the term CONTEXT-SPECIFIC does not appear.
343
344              -- http://www.obj-sys.com/asn1tutorial/node12.html */
345         $class = ($type >> 6) & 3;
346         switch ($class) {
347             case FILE_ASN1_CLASS_APPLICATION:
348             case FILE_ASN1_CLASS_PRIVATE:
349             case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
350                 if (!$constructed) {
351                     return array(
352                         'type'     => $class,
353                         'constant' => $tag,
354                         'content'  => $content,
355                         'length'   => $length + $start - $current['start']
356                     );
357                 }
358
359                 $newcontent = array();
360                 if (strlen($content)) {
361                     $newcontent = $this->_decode_ber($content, $start);
362                     $length = $newcontent['length'];
363                     if (substr($content, $length, 2) == "\0\0") {
364                         $length+= 2;
365                     }
366                     $start+= $length;
367                     $newcontent = array($newcontent);
368                 }
369
370                 return array(
371                     'type'     => $class,
372                     'constant' => $tag,
373                     // the array encapsulation is for BC with the old format
374                     'content'  => $newcontent,
375                     // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
376                     // the absence of $content['headerlength'] is how we know if something is indefinite or not.
377                     // technically, it could be defined to be 2 and then another indicator could be used but whatever.
378                     'length'   => $start - $current['start']
379                 ) + $current;
380         }
381
382         $current+= array('type' => $tag);
383
384         // decode UNIVERSAL tags
385         switch ($tag) {
386             case FILE_ASN1_TYPE_BOOLEAN:
387                 // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
388                 //if (strlen($content) != 1) {
389                 //    return false;
390                 //}
391                 $current['content'] = (bool) ord($content[0]);
392                 break;
393             case FILE_ASN1_TYPE_INTEGER:
394             case FILE_ASN1_TYPE_ENUMERATED:
395                 $current['content'] = new Math_BigInteger($content, -256);
396                 break;
397             case FILE_ASN1_TYPE_REAL: // not currently supported
398                 return false;
399             case FILE_ASN1_TYPE_BIT_STRING:
400                 // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
401                 // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
402                 // seven.
403                 if (!$constructed) {
404                     $current['content'] = $content;
405                 } else {
406                     $temp = $this->_decode_ber($content, $start);
407                     $length-= strlen($content);
408                     $last = count($temp) - 1;
409                     for ($i = 0; $i < $last; $i++) {
410                         // all subtags should be bit strings
411                         //if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
412                         //    return false;
413                         //}
414                         $current['content'].= substr($temp[$i]['content'], 1);
415                     }
416                     // all subtags should be bit strings
417                     //if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
418                     //    return false;
419                     //}
420                     $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
421                 }
422                 break;
423             case FILE_ASN1_TYPE_OCTET_STRING:
424                 if (!$constructed) {
425                     $current['content'] = $content;
426                 } else {
427                     $current['content'] = '';
428                     $length = 0;
429                     while (substr($content, 0, 2) != "\0\0") {
430                         $temp = $this->_decode_ber($content, $length + $start);
431                         $this->_string_shift($content, $temp['length']);
432                         // all subtags should be octet strings
433                         //if ($temp['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
434                         //    return false;
435                         //}
436                         $current['content'].= $temp['content'];
437                         $length+= $temp['length'];
438                     }
439                     if (substr($content, 0, 2) == "\0\0") {
440                         $length+= 2; // +2 for the EOC
441                     }
442                 }
443                 break;
444             case FILE_ASN1_TYPE_NULL:
445                 // "The contents octets shall not contain any octets." -- paragraph 8.8.2
446                 //if (strlen($content)) {
447                 //    return false;
448                 //}
449                 break;
450             case FILE_ASN1_TYPE_SEQUENCE:
451             case FILE_ASN1_TYPE_SET:
452                 $offset = 0;
453                 $current['content'] = array();
454                 while (strlen($content)) {
455                     // if indefinite length construction was used and we have an end-of-content string next
456                     // 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
457                     if (!isset($current['headerlength']) && substr($content, 0, 2) == "\0\0") {
458                         $length = $offset + 2; // +2 for the EOC
459                         break 2;
460                     }
461                     $temp = $this->_decode_ber($content, $start + $offset);
462                     $this->_string_shift($content, $temp['length']);
463                     $current['content'][] = $temp;
464                     $offset+= $temp['length'];
465                 }
466                 break;
467             case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
468                 $temp = ord($this->_string_shift($content));
469                 $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
470                 $valuen = 0;
471                 // process septets
472                 while (strlen($content)) {
473                     $temp = ord($this->_string_shift($content));
474                     $valuen <<= 7;
475                     $valuen |= $temp & 0x7F;
476                     if (~$temp & 0x80) {
477                         $current['content'].= ".$valuen";
478                         $valuen = 0;
479                     }
480                 }
481                 // the eighth bit of the last byte should not be 1
482                 //if ($temp >> 7) {
483                 //    return false;
484                 //}
485                 break;
486             /* Each character string type shall be encoded as if it had been declared:
487                [UNIVERSAL x] IMPLICIT OCTET STRING
488
489                  -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
490
491                Per that, we're not going to do any validation.  If there are any illegal characters in the string,
492                we don't really care */
493             case FILE_ASN1_TYPE_NUMERIC_STRING:
494                 // 0,1,2,3,4,5,6,7,8,9, and space
495             case FILE_ASN1_TYPE_PRINTABLE_STRING:
496                 // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
497                 // hyphen, full stop, solidus, colon, equal sign, question mark
498             case FILE_ASN1_TYPE_TELETEX_STRING:
499                 // The Teletex character set in CCITT's T61, space, and delete
500                 // see http://en.wikipedia.org/wiki/Teletex#Character_sets
501             case FILE_ASN1_TYPE_VIDEOTEX_STRING:
502                 // The Videotex character set in CCITT's T.100 and T.101, space, and delete
503             case FILE_ASN1_TYPE_VISIBLE_STRING:
504                 // Printing character sets of international ASCII, and space
505             case FILE_ASN1_TYPE_IA5_STRING:
506                 // International Alphabet 5 (International ASCII)
507             case FILE_ASN1_TYPE_GRAPHIC_STRING:
508                 // All registered G sets, and space
509             case FILE_ASN1_TYPE_GENERAL_STRING:
510                 // All registered C and G sets, space and delete
511             case FILE_ASN1_TYPE_UTF8_STRING:
512                 // ????
513             case FILE_ASN1_TYPE_BMP_STRING:
514                 $current['content'] = $content;
515                 break;
516             case FILE_ASN1_TYPE_UTC_TIME:
517             case FILE_ASN1_TYPE_GENERALIZED_TIME:
518                 $current['content'] = $this->_decodeTime($content, $tag);
519             default:
520         }
521
522         $start+= $length;
523
524         // ie. length is the length of the full TLV encoding - it's not just the length of the value
525         return $current + array('length' => $start - $current['start']);
526     }
527
528     /**
529      * ASN.1 Map
530      *
531      * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
532      *
533      * "Special" mappings may be applied on a per tag-name basis via $special.
534      *
535      * @param Array $decoded
536      * @param Array $mapping
537      * @param Array $special
538      * @return Array
539      * @access public
540      */
541     function asn1map($decoded, $mapping, $special = array())
542     {
543         if (isset($mapping['explicit']) && is_array($decoded['content'])) {
544             $decoded = $decoded['content'][0];
545         }
546
547         switch (true) {
548             case $mapping['type'] == FILE_ASN1_TYPE_ANY:
549                 $intype = $decoded['type'];
550                 if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ($this->encoded[$decoded['start']] & 0x20)) {
551                     return new File_ASN1_Element(substr($this->encoded, $decoded['start'], $decoded['length']));
552                 }
553                 $inmap = $this->ANYmap[$intype];
554                 if (is_string($inmap)) {
555                     return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
556                 }
557                 break;
558             case $mapping['type'] == FILE_ASN1_TYPE_CHOICE:
559                 foreach ($mapping['children'] as $key => $option) {
560                     switch (true) {
561                         case isset($option['constant']) && $option['constant'] == $decoded['constant']:
562                         case !isset($option['constant']) && $option['type'] == $decoded['type']:
563                             $value = $this->asn1map($decoded, $option, $special);
564                             break;
565                         case !isset($option['constant']) && $option['type'] == FILE_ASN1_TYPE_CHOICE:
566                             $v = $this->asn1map($decoded, $option, $special);
567                             if (isset($v)) {
568                                 $value = $v;
569                             }
570                     }
571                     if (isset($value)) {
572                         if (isset($special[$key])) {
573                             $value = call_user_func($special[$key], $value);
574                         }
575                         return array($key => $value);
576                     }
577                 }
578                 return null;
579             case isset($mapping['implicit']):
580             case isset($mapping['explicit']):
581             case $decoded['type'] == $mapping['type']:
582                 break;
583             default:
584                 // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
585                 // let it through
586                 switch (true) {
587                     case $decoded['type'] < 18: // FILE_ASN1_TYPE_NUMERIC_STRING == 18
588                     case $decoded['type'] > 30: // FILE_ASN1_TYPE_BMP_STRING == 30
589                     case $mapping['type'] < 18:
590                     case $mapping['type'] > 30:
591                         return null;
592                 }
593         }
594
595         if (isset($mapping['implicit'])) {
596             $decoded['type'] = $mapping['type'];
597         }
598
599         switch ($decoded['type']) {
600             case FILE_ASN1_TYPE_SEQUENCE:
601                 $map = array();
602
603                 // ignore the min and max
604                 if (isset($mapping['min']) && isset($mapping['max'])) {
605                     $child = $mapping['children'];
606                     foreach ($decoded['content'] as $content) {
607                         if (($map[] = $this->asn1map($content, $child, $special)) === null) {
608                             return null;
609                         }
610                     }
611
612                     return $map;
613                 }
614
615                 $n = count($decoded['content']);
616                 $i = 0;
617
618                 foreach ($mapping['children'] as $key => $child) {
619                     $maymatch = $i < $n; // Match only existing input.
620                     if ($maymatch) {
621                         $temp = $decoded['content'][$i];
622
623                         if ($child['type'] != FILE_ASN1_TYPE_CHOICE) {
624                             // Get the mapping and input class & constant.
625                             $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
626                             $constant = null;
627                             if (isset($temp['constant'])) {
628                                 $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
629                             }
630                             if (isset($child['class'])) {
631                                 $childClass = $child['class'];
632                                 $constant = $child['cast'];
633                             } elseif (isset($child['constant'])) {
634                                 $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
635                                 $constant = $child['constant'];
636                             }
637
638                             if (isset($constant) && isset($temp['constant'])) {
639                                 // Can only match if constants and class match.
640                                 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
641                             } else {
642                                 // Can only match if no constant expected and type matches or is generic.
643                                 $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], FILE_ASN1_TYPE_ANY, FILE_ASN1_TYPE_CHOICE)) !== false;
644                             }
645                         }
646                     }
647
648                     if ($maymatch) {
649                         // Attempt submapping.
650                         $candidate = $this->asn1map($temp, $child, $special);
651                         $maymatch = $candidate !== null;
652                     }
653
654                     if ($maymatch) {
655                         // Got the match: use it.
656                         if (isset($special[$key])) {
657                             $candidate = call_user_func($special[$key], $candidate);
658                         }
659                         $map[$key] = $candidate;
660                         $i++;
661                     } elseif (isset($child['default'])) {
662                         $map[$key] = $child['default']; // Use default.
663                     } elseif (!isset($child['optional'])) {
664                         return null; // Syntax error.
665                     }
666                 }
667
668                 // Fail mapping if all input items have not been consumed.
669                 return $i < $n? null: $map;
670
671             // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
672             case FILE_ASN1_TYPE_SET:
673                 $map = array();
674
675                 // ignore the min and max
676                 if (isset($mapping['min']) && isset($mapping['max'])) {
677                     $child = $mapping['children'];
678                     foreach ($decoded['content'] as $content) {
679                         if (($map[] = $this->asn1map($content, $child, $special)) === null) {
680                             return null;
681                         }
682                     }
683
684                     return $map;
685                 }
686
687                 for ($i = 0; $i < count($decoded['content']); $i++) {
688                     $temp = $decoded['content'][$i];
689                     $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
690                     if (isset($temp['constant'])) {
691                         $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
692                     }
693
694                     foreach ($mapping['children'] as $key => $child) {
695                         if (isset($map[$key])) {
696                             continue;
697                         }
698                         $maymatch = true;
699                         if ($child['type'] != FILE_ASN1_TYPE_CHOICE) {
700                             $childClass = FILE_ASN1_CLASS_UNIVERSAL;
701                             $constant = null;
702                             if (isset($child['class'])) {
703                                 $childClass = $child['class'];
704                                 $constant = $child['cast'];
705                             } elseif (isset($child['constant'])) {
706                                 $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
707                                 $constant = $child['constant'];
708                             }
709
710                             if (isset($constant) && isset($temp['constant'])) {
711                                 // Can only match if constants and class match.
712                                 $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
713                             } else {
714                                 // Can only match if no constant expected and type matches or is generic.
715                                 $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], FILE_ASN1_TYPE_ANY, FILE_ASN1_TYPE_CHOICE)) !== false;
716                             }
717                         }
718
719                         if ($maymatch) {
720                             // Attempt submapping.
721                             $candidate = $this->asn1map($temp, $child, $special);
722                             $maymatch = $candidate !== null;
723                         }
724
725                         if (!$maymatch) {
726                             break;
727                         }
728
729                         // Got the match: use it.
730                         if (isset($special[$key])) {
731                             $candidate = call_user_func($special[$key], $candidate);
732                         }
733                         $map[$key] = $candidate;
734                         break;
735                     }
736                 }
737
738                 foreach ($mapping['children'] as $key => $child) {
739                     if (!isset($map[$key])) {
740                         if (isset($child['default'])) {
741                             $map[$key] = $child['default'];
742                         } elseif (!isset($child['optional'])) {
743                             return null;
744                         }
745                     }
746                 }
747                 return $map;
748             case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
749                 return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
750             case FILE_ASN1_TYPE_UTC_TIME:
751             case FILE_ASN1_TYPE_GENERALIZED_TIME:
752                 if (isset($mapping['implicit'])) {
753                     $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
754                 }
755                 return @date($this->format, $decoded['content']);
756             case FILE_ASN1_TYPE_BIT_STRING:
757                 if (isset($mapping['mapping'])) {
758                     $offset = ord($decoded['content'][0]);
759                     $size = (strlen($decoded['content']) - 1) * 8 - $offset;
760                     /*
761                        From X.680-0207.pdf#page=46 (21.7):
762
763                        "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
764                         arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
765                         therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
766                         0 bits."
767                     */
768                     $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
769                     for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
770                         $current = ord($decoded['content'][$i]);
771                         for ($j = $offset; $j < 8; $j++) {
772                             $bits[] = (bool) ($current & (1 << $j));
773                         }
774                         $offset = 0;
775                     }
776                     $values = array();
777                     $map = array_reverse($mapping['mapping']);
778                     foreach ($map as $i => $value) {
779                         if ($bits[$i]) {
780                             $values[] = $value;
781                         }
782                     }
783                     return $values;
784                 }
785             case FILE_ASN1_TYPE_OCTET_STRING:
786                 return base64_encode($decoded['content']);
787             case FILE_ASN1_TYPE_NULL:
788                 return '';
789             case FILE_ASN1_TYPE_BOOLEAN:
790                 return $decoded['content'];
791             case FILE_ASN1_TYPE_NUMERIC_STRING:
792             case FILE_ASN1_TYPE_PRINTABLE_STRING:
793             case FILE_ASN1_TYPE_TELETEX_STRING:
794             case FILE_ASN1_TYPE_VIDEOTEX_STRING:
795             case FILE_ASN1_TYPE_IA5_STRING:
796             case FILE_ASN1_TYPE_GRAPHIC_STRING:
797             case FILE_ASN1_TYPE_VISIBLE_STRING:
798             case FILE_ASN1_TYPE_GENERAL_STRING:
799             case FILE_ASN1_TYPE_UNIVERSAL_STRING:
800             case FILE_ASN1_TYPE_UTF8_STRING:
801             case FILE_ASN1_TYPE_BMP_STRING:
802                 return $decoded['content'];
803             case FILE_ASN1_TYPE_INTEGER:
804             case FILE_ASN1_TYPE_ENUMERATED:
805                 $temp = $decoded['content'];
806                 if (isset($mapping['implicit'])) {
807                     $temp = new Math_BigInteger($decoded['content'], -256);
808                 }
809                 if (isset($mapping['mapping'])) {
810                     $temp = (int) $temp->toString();
811                     return isset($mapping['mapping'][$temp]) ?
812                         $mapping['mapping'][$temp] :
813                         false;
814                 }
815                 return $temp;
816         }
817     }
818
819     /**
820      * ASN.1 Encode
821      *
822      * DER-encodes an ASN.1 semantic mapping ($mapping).  Some libraries would probably call this function
823      * an ASN.1 compiler.
824      *
825      * "Special" mappings can be applied via $special.
826      *
827      * @param String $source
828      * @param String $mapping
829      * @param Integer $idx
830      * @return String
831      * @access public
832      */
833     function encodeDER($source, $mapping, $special = array())
834     {
835         $this->location = array();
836         return $this->_encode_der($source, $mapping, null, $special);
837     }
838
839     /**
840      * ASN.1 Encode (Helper function)
841      *
842      * @param String $source
843      * @param String $mapping
844      * @param Integer $idx
845      * @return String
846      * @access private
847      */
848     function _encode_der($source, $mapping, $idx = null, $special = array())
849     {
850         if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') {
851             return $source->element;
852         }
853
854         // do not encode (implicitly optional) fields with value set to default
855         if (isset($mapping['default']) && $source === $mapping['default']) {
856             return '';
857         }
858
859         if (isset($idx)) {
860             if (isset($special[$idx])) {
861                 $source = call_user_func($special[$idx], $source);
862             }
863             $this->location[] = $idx;
864         }
865
866         $tag = $mapping['type'];
867
868         switch ($tag) {
869             case FILE_ASN1_TYPE_SET:    // Children order is not important, thus process in sequence.
870             case FILE_ASN1_TYPE_SEQUENCE:
871                 $tag|= 0x20; // set the constructed bit
872                 $value = '';
873
874                 // ignore the min and max
875                 if (isset($mapping['min']) && isset($mapping['max'])) {
876                     $child = $mapping['children'];
877
878                     foreach ($source as $content) {
879                         $temp = $this->_encode_der($content, $child, null, $special);
880                         if ($temp === false) {
881                             return false;
882                         }
883                         $value.= $temp;
884                     }
885                     break;
886                 }
887
888                 foreach ($mapping['children'] as $key => $child) {
889                     if (!isset($source[$key])) {
890                         if (!isset($child['optional'])) {
891                             return false;
892                         }
893                         continue;
894                     }
895
896                     $temp = $this->_encode_der($source[$key], $child, $key, $special);
897                     if ($temp === false) {
898                         return false;
899                     }
900
901                     // An empty child encoding means it has been optimized out.
902                     // Else we should have at least one tag byte.
903                     if ($temp === '') {
904                         continue;
905                     }
906
907                     // if isset($child['constant']) is true then isset($child['optional']) should be true as well
908                     if (isset($child['constant'])) {
909                         /*
910                            From X.680-0207.pdf#page=58 (30.6):
911
912                            "The tagging construction specifies explicit tagging if any of the following holds:
913                             ...
914                             c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
915                             AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
916                             an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
917                          */
918                         if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
919                             $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
920                             $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
921                         } else {
922                             $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
923                             $temp = $subtag . substr($temp, 1);
924                         }
925                     }
926                     $value.= $temp;
927                 }
928                 break;
929             case FILE_ASN1_TYPE_CHOICE:
930                 $temp = false;
931
932                 foreach ($mapping['children'] as $key => $child) {
933                     if (!isset($source[$key])) {
934                         continue;
935                     }
936
937                     $temp = $this->_encode_der($source[$key], $child, $key, $special);
938                     if ($temp === false) {
939                         return false;
940                     }
941
942                     // An empty child encoding means it has been optimized out.
943                     // Else we should have at least one tag byte.
944                     if ($temp === '') {
945                         continue;
946                     }
947
948                     $tag = ord($temp[0]);
949
950                     // if isset($child['constant']) is true then isset($child['optional']) should be true as well
951                     if (isset($child['constant'])) {
952                         if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
953                             $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
954                             $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
955                         } else {
956                             $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
957                             $temp = $subtag . substr($temp, 1);
958                         }
959                     }
960                 }
961
962                 if (isset($idx)) {
963                     array_pop($this->location);
964                 }
965
966                 if ($temp && isset($mapping['cast'])) {
967                     $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
968                 }
969
970                 return $temp;
971             case FILE_ASN1_TYPE_INTEGER:
972             case FILE_ASN1_TYPE_ENUMERATED:
973                 if (!isset($mapping['mapping'])) {
974                     if (is_numeric($source)) {
975                         $source = new Math_BigInteger($source);
976                     }
977                     $value = $source->toBytes(true);
978                 } else {
979                     $value = array_search($source, $mapping['mapping']);
980                     if ($value === false) {
981                         return false;
982                     }
983                     $value = new Math_BigInteger($value);
984                     $value = $value->toBytes(true);
985                 }
986                 if (!strlen($value)) {
987                     $value = chr(0);
988                 }
989                 break;
990             case FILE_ASN1_TYPE_UTC_TIME:
991             case FILE_ASN1_TYPE_GENERALIZED_TIME:
992                 $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y';
993                 $format.= 'mdHis';
994                 $value = @gmdate($format, strtotime($source)) . 'Z';
995                 break;
996             case FILE_ASN1_TYPE_BIT_STRING:
997                 if (isset($mapping['mapping'])) {
998                     $bits = array_fill(0, count($mapping['mapping']), 0);
999                     $size = 0;
1000                     for ($i = 0; $i < count($mapping['mapping']); $i++) {
1001                         if (in_array($mapping['mapping'][$i], $source)) {
1002                             $bits[$i] = 1;
1003                             $size = $i;
1004                         }
1005                     }
1006
1007                     if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
1008                         $size = $mapping['min'] - 1;
1009                     }
1010
1011                     $offset = 8 - (($size + 1) & 7);
1012                     $offset = $offset !== 8 ? $offset : 0;
1013
1014                     $value = chr($offset);
1015
1016                     for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
1017                         unset($bits[$i]);
1018                     }
1019
1020                     $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
1021                     $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
1022                     foreach ($bytes as $byte) {
1023                         $value.= chr(bindec($byte));
1024                     }
1025
1026                     break;
1027                 }
1028             case FILE_ASN1_TYPE_OCTET_STRING:
1029                 /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
1030                    the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
1031
1032                    -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
1033                 $value = base64_decode($source);
1034                 break;
1035             case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
1036                 $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
1037                 if ($oid === false) {
1038                     user_error('Invalid OID');
1039                     return false;
1040                 }
1041                 $value = '';
1042                 $parts = explode('.', $oid);
1043                 $value = chr(40 * $parts[0] + $parts[1]);
1044                 for ($i = 2; $i < count($parts); $i++) {
1045                     $temp = '';
1046                     if (!$parts[$i]) {
1047                         $temp = "\0";
1048                     } else {
1049                         while ($parts[$i]) {
1050                             $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
1051                             $parts[$i] >>= 7;
1052                         }
1053                         $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
1054                     }
1055                     $value.= $temp;
1056                 }
1057                 break;
1058             case FILE_ASN1_TYPE_ANY:
1059                 $loc = $this->location;
1060                 if (isset($idx)) {
1061                     array_pop($this->location);
1062                 }
1063
1064                 switch (true) {
1065                     case !isset($source):
1066                         return $this->_encode_der(null, array('type' => FILE_ASN1_TYPE_NULL) + $mapping, null, $special);
1067                     case is_int($source):
1068                     case is_object($source) && strtolower(get_class($source)) == 'math_biginteger':
1069                         return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping, null, $special);
1070                     case is_float($source):
1071                         return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping, null, $special);
1072                     case is_bool($source):
1073                         return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping, null, $special);
1074                     case is_array($source) && count($source) == 1:
1075                         $typename = implode('', array_keys($source));
1076                         $outtype = array_search($typename, $this->ANYmap, true);
1077                         if ($outtype !== false) {
1078                             return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
1079                         }
1080                     }
1081
1082                 $filters = $this->filters;
1083                 foreach ($loc as $part) {
1084                     if (!isset($filters[$part])) {
1085                         $filters = false;
1086                         break;
1087                     }
1088                     $filters = $filters[$part];
1089                 }
1090                 if ($filters === false) {
1091                     user_error('No filters defined for ' . implode('/', $loc));
1092                     return false;
1093                 }
1094                 return $this->_encode_der($source, $filters + $mapping, null, $special);
1095             case FILE_ASN1_TYPE_NULL:
1096                 $value = '';
1097                 break;
1098             case FILE_ASN1_TYPE_NUMERIC_STRING:
1099             case FILE_ASN1_TYPE_TELETEX_STRING:
1100             case FILE_ASN1_TYPE_PRINTABLE_STRING:
1101             case FILE_ASN1_TYPE_UNIVERSAL_STRING:
1102             case FILE_ASN1_TYPE_UTF8_STRING:
1103             case FILE_ASN1_TYPE_BMP_STRING:
1104             case FILE_ASN1_TYPE_IA5_STRING:
1105             case FILE_ASN1_TYPE_VISIBLE_STRING:
1106             case FILE_ASN1_TYPE_VIDEOTEX_STRING:
1107             case FILE_ASN1_TYPE_GRAPHIC_STRING:
1108             case FILE_ASN1_TYPE_GENERAL_STRING:
1109                 $value = $source;
1110                 break;
1111             case FILE_ASN1_TYPE_BOOLEAN:
1112                 $value = $source ? "\xFF" : "\x00";
1113                 break;
1114             default:
1115                 user_error('Mapping provides no type definition for ' . implode('/', $this->location));
1116                 return false;
1117         }
1118
1119         if (isset($idx)) {
1120             array_pop($this->location);
1121         }
1122
1123         if (isset($mapping['cast'])) {
1124             if (isset($mapping['explicit']) || $mapping['type'] == FILE_ASN1_TYPE_CHOICE) {
1125                 $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1126                 $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
1127             } else {
1128                 $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
1129             }
1130         }
1131
1132         return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1133     }
1134
1135     /**
1136      * DER-encode the length
1137      *
1138      * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
1139      * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
1140      *
1141      * @access private
1142      * @param Integer $length
1143      * @return String
1144      */
1145     function _encodeLength($length)
1146     {
1147         if ($length <= 0x7F) {
1148             return chr($length);
1149         }
1150
1151         $temp = ltrim(pack('N', $length), chr(0));
1152         return pack('Ca*', 0x80 | strlen($temp), $temp);
1153     }
1154
1155     /**
1156      * BER-decode the time
1157      *
1158      * Called by _decode_ber() and in the case of implicit tags asn1map().
1159      *
1160      * @access private
1161      * @param String $content
1162      * @param Integer $tag
1163      * @return String
1164      */
1165     function _decodeTime($content, $tag)
1166     {
1167         /* UTCTime:
1168            http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1169            http://www.obj-sys.com/asn1tutorial/node15.html
1170
1171            GeneralizedTime:
1172            http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1173            http://www.obj-sys.com/asn1tutorial/node14.html */
1174
1175         $pattern = $tag == FILE_ASN1_TYPE_UTC_TIME ?
1176             '#(..)(..)(..)(..)(..)(..)(.*)#' :
1177             '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';
1178
1179         preg_match($pattern, $content, $matches);
1180
1181         list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;
1182
1183         if ($tag == FILE_ASN1_TYPE_UTC_TIME) {
1184             $year = $year >= 50 ? "19$year" : "20$year";
1185         }
1186
1187         if ($timezone == 'Z') {
1188             $mktime = 'gmmktime';
1189             $timezone = 0;
1190         } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
1191             $mktime = 'gmmktime';
1192             $timezone = 60 * $matches[3] + 3600 * $matches[2];
1193             if ($matches[1] == '-') {
1194                 $timezone = -$timezone;
1195             }
1196         } else {
1197             $mktime = 'mktime';
1198             $timezone = 0;
1199         }
1200
1201         return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
1202     }
1203
1204     /**
1205      * Set the time format
1206      *
1207      * Sets the time / date format for asn1map().
1208      *
1209      * @access public
1210      * @param String $format
1211      */
1212     function setTimeFormat($format)
1213     {
1214         $this->format = $format;
1215     }
1216
1217     /**
1218      * Load OIDs
1219      *
1220      * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1221      *
1222      * @access public
1223      * @param Array $oids
1224      */
1225     function loadOIDs($oids)
1226     {
1227         $this->oids = $oids;
1228     }
1229
1230     /**
1231      * Load filters
1232      *
1233      * See File_X509, etc, for an example.
1234      *
1235      * @access public
1236      * @param Array $filters
1237      */
1238     function loadFilters($filters)
1239     {
1240         $this->filters = $filters;
1241     }
1242
1243     /**
1244      * String Shift
1245      *
1246      * Inspired by array_shift
1247      *
1248      * @param String $string
1249      * @param optional Integer $index
1250      * @return String
1251      * @access private
1252      */
1253     function _string_shift(&$string, $index = 1)
1254     {
1255         $substr = substr($string, 0, $index);
1256         $string = substr($string, $index);
1257         return $substr;
1258     }
1259
1260     /**
1261      * String type conversion
1262      *
1263      * This is a lazy conversion, dealing only with character size.
1264      * No real conversion table is used.
1265      *
1266      * @param String $in
1267      * @param optional Integer $from
1268      * @param optional Integer $to
1269      * @return String
1270      * @access public
1271      */
1272     function convert($in, $from = FILE_ASN1_TYPE_UTF8_STRING, $to = FILE_ASN1_TYPE_UTF8_STRING)
1273     {
1274         if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1275             return false;
1276         }
1277         $insize = $this->stringTypeSize[$from];
1278         $outsize = $this->stringTypeSize[$to];
1279         $inlength = strlen($in);
1280         $out = '';
1281
1282         for ($i = 0; $i < $inlength;) {
1283             if ($inlength - $i < $insize) {
1284                 return false;
1285             }
1286
1287             // Get an input character as a 32-bit value.
1288             $c = ord($in[$i++]);
1289             switch (true) {
1290                 case $insize == 4:
1291                     $c = ($c << 8) | ord($in[$i++]);
1292                     $c = ($c << 8) | ord($in[$i++]);
1293                 case $insize == 2:
1294                     $c = ($c << 8) | ord($in[$i++]);
1295                 case $insize == 1:
1296                     break;
1297                 case ($c & 0x80) == 0x00:
1298                     break;
1299                 case ($c & 0x40) == 0x00:
1300                     return false;
1301                 default:
1302                     $bit = 6;
1303                     do {
1304                         if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1305                             return false;
1306                         }
1307                         $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1308                         $bit += 5;
1309                         $mask = 1 << $bit;
1310                     } while ($c & $bit);
1311                     $c &= $mask - 1;
1312                     break;
1313             }
1314
1315             // Convert and append the character to output string.
1316             $v = '';
1317             switch (true) {
1318                 case $outsize == 4:
1319                     $v .= chr($c & 0xFF);
1320                     $c >>= 8;
1321                     $v .= chr($c & 0xFF);
1322                     $c >>= 8;
1323                 case $outsize == 2:
1324                     $v .= chr($c & 0xFF);
1325                     $c >>= 8;
1326                 case $outsize == 1:
1327                     $v .= chr($c & 0xFF);
1328                     $c >>= 8;
1329                     if ($c) {
1330                         return false;
1331                     }
1332                     break;
1333                 case ($c & 0x80000000) != 0:
1334                     return false;
1335                 case $c >= 0x04000000:
1336                     $v .= chr(0x80 | ($c & 0x3F));
1337                     $c = ($c >> 6) | 0x04000000;
1338                 case $c >= 0x00200000:
1339                     $v .= chr(0x80 | ($c & 0x3F));
1340                     $c = ($c >> 6) | 0x00200000;
1341                 case $c >= 0x00010000:
1342                     $v .= chr(0x80 | ($c & 0x3F));
1343                     $c = ($c >> 6) | 0x00010000;
1344                 case $c >= 0x00000800:
1345                     $v .= chr(0x80 | ($c & 0x3F));
1346                     $c = ($c >> 6) | 0x00000800;
1347                 case $c >= 0x00000080:
1348                     $v .= chr(0x80 | ($c & 0x3F));
1349                     $c = ($c >> 6) | 0x000000C0;
1350                 default:
1351                     $v .= chr($c);
1352                     break;
1353             }
1354             $out .= strrev($v);
1355         }
1356         return $out;
1357     }
1358 }