1<?php
2
3/**
4 * Pure-PHP ASN.1 Parser
5 *
6 * PHP version 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 * \phpseclib\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 * @category  File
17 * @package   ASN1
18 * @author    Jim Wigginton <terrafrost@php.net>
19 * @copyright 2012 Jim Wigginton
20 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
21 * @link      http://phpseclib.sourceforge.net
22 */
23
24namespace phpseclib\File;
25
26use phpseclib\File\ASN1\Element;
27use phpseclib\Math\BigInteger;
28use DateTime;
29use DateTimeZone;
30
31/**
32 * Pure-PHP ASN.1 Parser
33 *
34 * @package ASN1
35 * @author  Jim Wigginton <terrafrost@php.net>
36 * @access  public
37 */
38class ASN1
39{
40    /**#@+
41     * Tag Classes
42     *
43     * @access private
44     * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
45     */
46    const CLASS_UNIVERSAL        = 0;
47    const CLASS_APPLICATION      = 1;
48    const CLASS_CONTEXT_SPECIFIC = 2;
49    const CLASS_PRIVATE          = 3;
50    /**#@-*/
51
52    /**#@+
53     * Tag Classes
54     *
55     * @access private
56     * @link http://www.obj-sys.com/asn1tutorial/node124.html
57    */
58    const TYPE_BOOLEAN           = 1;
59    const TYPE_INTEGER           = 2;
60    const TYPE_BIT_STRING        = 3;
61    const TYPE_OCTET_STRING      = 4;
62    const TYPE_NULL              = 5;
63    const TYPE_OBJECT_IDENTIFIER = 6;
64    //const TYPE_OBJECT_DESCRIPTOR = 7;
65    //const TYPE_INSTANCE_OF       = 8; // EXTERNAL
66    const TYPE_REAL              = 9;
67    const TYPE_ENUMERATED        = 10;
68    //const TYPE_EMBEDDED          = 11;
69    const TYPE_UTF8_STRING       = 12;
70    //const TYPE_RELATIVE_OID      = 13;
71    const TYPE_SEQUENCE          = 16; // SEQUENCE OF
72    const TYPE_SET               = 17; // SET OF
73    /**#@-*/
74    /**#@+
75     * More Tag Classes
76     *
77     * @access private
78     * @link http://www.obj-sys.com/asn1tutorial/node10.html
79    */
80    const TYPE_NUMERIC_STRING   = 18;
81    const TYPE_PRINTABLE_STRING = 19;
82    const TYPE_TELETEX_STRING   = 20; // T61String
83    const TYPE_VIDEOTEX_STRING  = 21;
84    const TYPE_IA5_STRING       = 22;
85    const TYPE_UTC_TIME         = 23;
86    const TYPE_GENERALIZED_TIME = 24;
87    const TYPE_GRAPHIC_STRING   = 25;
88    const TYPE_VISIBLE_STRING   = 26; // ISO646String
89    const TYPE_GENERAL_STRING   = 27;
90    const TYPE_UNIVERSAL_STRING = 28;
91    //const TYPE_CHARACTER_STRING = 29;
92    const TYPE_BMP_STRING       = 30;
93    /**#@-*/
94
95    /**#@+
96     * Tag Aliases
97     *
98     * These tags are kinda place holders for other tags.
99     *
100     * @access private
101    */
102    const TYPE_CHOICE = -1;
103    const TYPE_ANY    = -2;
104    /**#@-*/
105
106    /**
107     * ASN.1 object identifier
108     *
109     * @var array
110     * @access private
111     * @link http://en.wikipedia.org/wiki/Object_identifier
112     */
113    var $oids = array();
114
115    /**
116     * Default date format
117     *
118     * @var string
119     * @access private
120     * @link http://php.net/class.datetime
121     */
122    var $format = 'D, d M Y H:i:s O';
123
124    /**
125     * Default date format
126     *
127     * @var array
128     * @access private
129     * @see self::setTimeFormat()
130     * @see self::asn1map()
131     * @link http://php.net/class.datetime
132     */
133    var $encoded;
134
135    /**
136     * Filters
137     *
138     * If the mapping type is self::TYPE_ANY what do we actually encode it as?
139     *
140     * @var array
141     * @access private
142     * @see self::_encode_der()
143     */
144    var $filters;
145
146    /**
147     * Type mapping table for the ANY type.
148     *
149     * Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element.
150     * Unambiguous types get the direct mapping (int/real/bool).
151     * Others are mapped as a choice, with an extra indexing level.
152     *
153     * @var array
154     * @access public
155     */
156    var $ANYmap = array(
157        self::TYPE_BOOLEAN              => true,
158        self::TYPE_INTEGER              => true,
159        self::TYPE_BIT_STRING           => 'bitString',
160        self::TYPE_OCTET_STRING         => 'octetString',
161        self::TYPE_NULL                 => 'null',
162        self::TYPE_OBJECT_IDENTIFIER    => 'objectIdentifier',
163        self::TYPE_REAL                 => true,
164        self::TYPE_ENUMERATED           => 'enumerated',
165        self::TYPE_UTF8_STRING          => 'utf8String',
166        self::TYPE_NUMERIC_STRING       => 'numericString',
167        self::TYPE_PRINTABLE_STRING     => 'printableString',
168        self::TYPE_TELETEX_STRING       => 'teletexString',
169        self::TYPE_VIDEOTEX_STRING      => 'videotexString',
170        self::TYPE_IA5_STRING           => 'ia5String',
171        self::TYPE_UTC_TIME             => 'utcTime',
172        self::TYPE_GENERALIZED_TIME     => 'generalTime',
173        self::TYPE_GRAPHIC_STRING       => 'graphicString',
174        self::TYPE_VISIBLE_STRING       => 'visibleString',
175        self::TYPE_GENERAL_STRING       => 'generalString',
176        self::TYPE_UNIVERSAL_STRING     => 'universalString',
177        //self::TYPE_CHARACTER_STRING     => 'characterString',
178        self::TYPE_BMP_STRING           => 'bmpString'
179    );
180
181    /**
182     * String type to character size mapping table.
183     *
184     * Non-convertable types are absent from this table.
185     * size == 0 indicates variable length encoding.
186     *
187     * @var array
188     * @access public
189     */
190    var $stringTypeSize = array(
191        self::TYPE_UTF8_STRING      => 0,
192        self::TYPE_BMP_STRING       => 2,
193        self::TYPE_UNIVERSAL_STRING => 4,
194        self::TYPE_PRINTABLE_STRING => 1,
195        self::TYPE_TELETEX_STRING   => 1,
196        self::TYPE_IA5_STRING       => 1,
197        self::TYPE_VISIBLE_STRING   => 1,
198    );
199
200    /**
201     * Parse BER-encoding
202     *
203     * Serves a similar purpose to openssl's asn1parse
204     *
205     * @param string $encoded
206     * @return array
207     * @access public
208     */
209    function decodeBER($encoded)
210    {
211        if ($encoded instanceof Element) {
212            $encoded = $encoded->element;
213        }
214
215        $this->encoded = $encoded;
216        // encapsulate in an array for BC with the old decodeBER
217        return array($this->_decode_ber($encoded));
218    }
219
220    /**
221     * Parse BER-encoding (Helper function)
222     *
223     * Sometimes we want to get the BER encoding of a particular tag.  $start lets us do that without having to reencode.
224     * $encoded is passed by reference for the recursive calls done for self::TYPE_BIT_STRING and
225     * self::TYPE_OCTET_STRING. In those cases, the indefinite length is used.
226     *
227     * @param string $encoded
228     * @param int $start
229     * @param int $encoded_pos
230     * @return array
231     * @access private
232     */
233    function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
234    {
235        $current = array('start' => $start);
236
237        if (!isset($encoded[$encoded_pos])) {
238            return false;
239        }
240        $type = ord($encoded[$encoded_pos++]);
241        $startOffset = 1;
242
243        $constructed = ($type >> 5) & 1;
244
245        $tag = $type & 0x1F;
246        if ($tag == 0x1F) {
247            $tag = 0;
248            // process septets (since the eighth bit is ignored, it's not an octet)
249            do {
250                if (!isset($encoded[$encoded_pos])) {
251                    return false;
252                }
253                $temp = ord($encoded[$encoded_pos++]);
254                $startOffset++;
255                $loop = $temp >> 7;
256                $tag <<= 7;
257                $temp &= 0x7F;
258                // "bits 7 to 1 of the first subsequent octet shall not all be zero"
259                if ($startOffset == 2 && $temp == 0) {
260                    return false;
261                }
262                $tag |= $temp;
263            } while ($loop);
264        }
265
266        $start+= $startOffset;
267
268        // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
269        if (!isset($encoded[$encoded_pos])) {
270            return false;
271        }
272        $length = ord($encoded[$encoded_pos++]);
273        $start++;
274        if ($length == 0x80) { // indefinite length
275            // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
276            //  immediately available." -- paragraph 8.1.3.2.c
277            $length = strlen($encoded) - $encoded_pos;
278        } elseif ($length & 0x80) { // definite length, long form
279            // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
280            // support it up to four.
281            $length&= 0x7F;
282            $temp = substr($encoded, $encoded_pos, $length);
283            $encoded_pos += $length;
284            // tags of indefinte length don't really have a header length; this length includes the tag
285            $current+= array('headerlength' => $length + 2);
286            $start+= $length;
287            extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
288        } else {
289            $current+= array('headerlength' => 2);
290        }
291
292        if ($length > (strlen($encoded) - $encoded_pos)) {
293            return false;
294        }
295
296        $content = substr($encoded, $encoded_pos, $length);
297        $content_pos = 0;
298
299        // at this point $length can be overwritten. it's only accurate for definite length things as is
300
301        /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
302           built-in types. It defines an application-independent data type that must be distinguishable from all other
303           data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
304           have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
305           a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
306           alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
307           data type; the term CONTEXT-SPECIFIC does not appear.
308
309             -- http://www.obj-sys.com/asn1tutorial/node12.html */
310        $class = ($type >> 6) & 3;
311        switch ($class) {
312            case self::CLASS_APPLICATION:
313            case self::CLASS_PRIVATE:
314            case self::CLASS_CONTEXT_SPECIFIC:
315                if (!$constructed) {
316                    return array(
317                        'type'     => $class,
318                        'constant' => $tag,
319                        'content'  => $content,
320                        'length'   => $length + $start - $current['start']
321                    );
322                }
323
324                $newcontent = array();
325                $remainingLength = $length;
326                while ($remainingLength > 0) {
327                    $temp = $this->_decode_ber($content, $start, $content_pos);
328                    if ($temp === false) {
329                        break;
330                    }
331                    $length = $temp['length'];
332                    // end-of-content octets - see paragraph 8.1.5
333                    if (substr($content, $content_pos + $length, 2) == "\0\0") {
334                        $length+= 2;
335                        $start+= $length;
336                        $newcontent[] = $temp;
337                        break;
338                    }
339                    $start+= $length;
340                    $remainingLength-= $length;
341                    $newcontent[] = $temp;
342                    $content_pos += $length;
343                }
344
345                return array(
346                    'type'     => $class,
347                    'constant' => $tag,
348                    // the array encapsulation is for BC with the old format
349                    'content'  => $newcontent,
350                    // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
351                    // the absence of $content['headerlength'] is how we know if something is indefinite or not.
352                    // technically, it could be defined to be 2 and then another indicator could be used but whatever.
353                    'length'   => $start - $current['start']
354                ) + $current;
355        }
356
357        $current+= array('type' => $tag);
358
359        // decode UNIVERSAL tags
360        switch ($tag) {
361            case self::TYPE_BOOLEAN:
362                // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
363                if ($constructed || strlen($content) != 1) {
364                    return false;
365                }
366                $current['content'] = (bool) ord($content[$content_pos]);
367                break;
368            case self::TYPE_INTEGER:
369            case self::TYPE_ENUMERATED:
370                if ($constructed) {
371                    return false;
372                }
373                $current['content'] = new BigInteger(substr($content, $content_pos), -256);
374                break;
375            case self::TYPE_REAL: // not currently supported
376                return false;
377            case self::TYPE_BIT_STRING:
378                // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
379                // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
380                // seven.
381                if (!$constructed) {
382                    $current['content'] = substr($content, $content_pos);
383                } else {
384                    $temp = $this->_decode_ber($content, $start, $content_pos);
385                    if ($temp === false) {
386                        return false;
387                    }
388                    $length-= (strlen($content) - $content_pos);
389                    $last = count($temp) - 1;
390                    for ($i = 0; $i < $last; $i++) {
391                        // all subtags should be bit strings
392                        if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
393                            return false;
394                        }
395                        $current['content'].= substr($temp[$i]['content'], 1);
396                    }
397                    // all subtags should be bit strings
398                    if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
399                        return false;
400                    }
401                    $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
402                }
403                break;
404            case self::TYPE_OCTET_STRING:
405                if (!$constructed) {
406                    $current['content'] = substr($content, $content_pos);
407                } else {
408                    $current['content'] = '';
409                    $length = 0;
410                    while (substr($content, $content_pos, 2) != "\0\0") {
411                        $temp = $this->_decode_ber($content, $length + $start, $content_pos);
412                        if ($temp === false) {
413                            return false;
414                        }
415                        $content_pos += $temp['length'];
416                        // all subtags should be octet strings
417                        if ($temp['type'] != self::TYPE_OCTET_STRING) {
418                            return false;
419                        }
420                        $current['content'].= $temp['content'];
421                        $length+= $temp['length'];
422                    }
423                    if (substr($content, $content_pos, 2) == "\0\0") {
424                        $length+= 2; // +2 for the EOC
425                    }
426                }
427                break;
428            case self::TYPE_NULL:
429                // "The contents octets shall not contain any octets." -- paragraph 8.8.2
430                if ($constructed || strlen($content)) {
431                    return false;
432                }
433                break;
434            case self::TYPE_SEQUENCE:
435            case self::TYPE_SET:
436                if (!$constructed) {
437                    return false;
438                }
439                $offset = 0;
440                $current['content'] = array();
441                $content_len = strlen($content);
442                while ($content_pos < $content_len) {
443                    // if indefinite length construction was used and we have an end-of-content string next
444                    // 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
445                    if (!isset($current['headerlength']) && substr($content, $content_pos, 2) == "\0\0") {
446                        $length = $offset + 2; // +2 for the EOC
447                        break 2;
448                    }
449                    $temp = $this->_decode_ber($content, $start + $offset, $content_pos);
450                    if ($temp === false) {
451                        return false;
452                    }
453                    $content_pos += $temp['length'];
454                    $current['content'][] = $temp;
455                    $offset+= $temp['length'];
456                }
457                break;
458            case self::TYPE_OBJECT_IDENTIFIER:
459                if ($constructed) {
460                    return false;
461                }
462                $current['content'] = $this->_decodeOID(substr($content, $content_pos));
463                if ($current['content'] === false) {
464                    return false;
465                }
466                break;
467            /* Each character string type shall be encoded as if it had been declared:
468               [UNIVERSAL x] IMPLICIT OCTET STRING
469
470                 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
471
472               Per that, we're not going to do any validation.  If there are any illegal characters in the string,
473               we don't really care */
474            case self::TYPE_NUMERIC_STRING:
475                // 0,1,2,3,4,5,6,7,8,9, and space
476            case self::TYPE_PRINTABLE_STRING:
477                // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
478                // hyphen, full stop, solidus, colon, equal sign, question mark
479            case self::TYPE_TELETEX_STRING:
480                // The Teletex character set in CCITT's T61, space, and delete
481                // see http://en.wikipedia.org/wiki/Teletex#Character_sets
482            case self::TYPE_VIDEOTEX_STRING:
483                // The Videotex character set in CCITT's T.100 and T.101, space, and delete
484            case self::TYPE_VISIBLE_STRING:
485                // Printing character sets of international ASCII, and space
486            case self::TYPE_IA5_STRING:
487                // International Alphabet 5 (International ASCII)
488            case self::TYPE_GRAPHIC_STRING:
489                // All registered G sets, and space
490            case self::TYPE_GENERAL_STRING:
491                // All registered C and G sets, space and delete
492            case self::TYPE_UTF8_STRING:
493                // ????
494            case self::TYPE_BMP_STRING:
495                if ($constructed) {
496                    return false;
497                }
498                $current['content'] = substr($content, $content_pos);
499                break;
500            case self::TYPE_UTC_TIME:
501            case self::TYPE_GENERALIZED_TIME:
502                if ($constructed) {
503                    return false;
504                }
505                $current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag);
506                break;
507            default:
508                return false;
509        }
510
511        $start+= $length;
512
513        // ie. length is the length of the full TLV encoding - it's not just the length of the value
514        return $current + array('length' => $start - $current['start']);
515    }
516
517    /**
518     * ASN.1 Map
519     *
520     * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
521     *
522     * "Special" mappings may be applied on a per tag-name basis via $special.
523     *
524     * @param array $decoded
525     * @param array $mapping
526     * @param array $special
527     * @return array
528     * @access public
529     */
530    function asn1map($decoded, $mapping, $special = array())
531    {
532        if (!is_array($decoded)) {
533            return false;
534        }
535
536        if (isset($mapping['explicit']) && is_array($decoded['content'])) {
537            $decoded = $decoded['content'][0];
538        }
539
540        switch (true) {
541            case $mapping['type'] == self::TYPE_ANY:
542                $intype = $decoded['type'];
543                if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || (ord($this->encoded[$decoded['start']]) & 0x20)) {
544                    return new Element(substr($this->encoded, $decoded['start'], $decoded['length']));
545                }
546                $inmap = $this->ANYmap[$intype];
547                if (is_string($inmap)) {
548                    return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
549                }
550                break;
551            case $mapping['type'] == self::TYPE_CHOICE:
552                foreach ($mapping['children'] as $key => $option) {
553                    switch (true) {
554                        case isset($option['constant']) && $option['constant'] == $decoded['constant']:
555                        case !isset($option['constant']) && $option['type'] == $decoded['type']:
556                            $value = $this->asn1map($decoded, $option, $special);
557                            break;
558                        case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
559                            $v = $this->asn1map($decoded, $option, $special);
560                            if (isset($v)) {
561                                $value = $v;
562                            }
563                    }
564                    if (isset($value)) {
565                        if (isset($special[$key])) {
566                            $value = call_user_func($special[$key], $value);
567                        }
568                        return array($key => $value);
569                    }
570                }
571                return null;
572            case isset($mapping['implicit']):
573            case isset($mapping['explicit']):
574            case $decoded['type'] == $mapping['type']:
575                break;
576            default:
577                // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
578                // let it through
579                switch (true) {
580                    case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18
581                    case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30
582                    case $mapping['type'] < 18:
583                    case $mapping['type'] > 30:
584                        return null;
585                }
586        }
587
588        if (isset($mapping['implicit'])) {
589            $decoded['type'] = $mapping['type'];
590        }
591
592        switch ($decoded['type']) {
593            case self::TYPE_SEQUENCE:
594                $map = array();
595
596                // ignore the min and max
597                if (isset($mapping['min']) && isset($mapping['max'])) {
598                    $child = $mapping['children'];
599                    foreach ($decoded['content'] as $content) {
600                        if (($map[] = $this->asn1map($content, $child, $special)) === null) {
601                            return null;
602                        }
603                    }
604
605                    return $map;
606                }
607
608                $n = count($decoded['content']);
609                $i = 0;
610
611                foreach ($mapping['children'] as $key => $child) {
612                    $maymatch = $i < $n; // Match only existing input.
613                    if ($maymatch) {
614                        $temp = $decoded['content'][$i];
615
616                        if ($child['type'] != self::TYPE_CHOICE) {
617                            // Get the mapping and input class & constant.
618                            $childClass = $tempClass = self::CLASS_UNIVERSAL;
619                            $constant = null;
620                            if (isset($temp['constant'])) {
621                                $tempClass = $temp['type'];
622                            }
623                            if (isset($child['class'])) {
624                                $childClass = $child['class'];
625                                $constant = $child['cast'];
626                            } elseif (isset($child['constant'])) {
627                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
628                                $constant = $child['constant'];
629                            }
630
631                            if (isset($constant) && isset($temp['constant'])) {
632                                // Can only match if constants and class match.
633                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
634                            } else {
635                                // Can only match if no constant expected and type matches or is generic.
636                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
637                            }
638                        }
639                    }
640
641                    if ($maymatch) {
642                        // Attempt submapping.
643                        $candidate = $this->asn1map($temp, $child, $special);
644                        $maymatch = $candidate !== null;
645                    }
646
647                    if ($maymatch) {
648                        // Got the match: use it.
649                        if (isset($special[$key])) {
650                            $candidate = call_user_func($special[$key], $candidate);
651                        }
652                        $map[$key] = $candidate;
653                        $i++;
654                    } elseif (isset($child['default'])) {
655                        $map[$key] = $child['default']; // Use default.
656                    } elseif (!isset($child['optional'])) {
657                        return null; // Syntax error.
658                    }
659                }
660
661                // Fail mapping if all input items have not been consumed.
662                return $i < $n ? null: $map;
663
664            // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
665            case self::TYPE_SET:
666                $map = array();
667
668                // ignore the min and max
669                if (isset($mapping['min']) && isset($mapping['max'])) {
670                    $child = $mapping['children'];
671                    foreach ($decoded['content'] as $content) {
672                        if (($map[] = $this->asn1map($content, $child, $special)) === null) {
673                            return null;
674                        }
675                    }
676
677                    return $map;
678                }
679
680                for ($i = 0; $i < count($decoded['content']); $i++) {
681                    $temp = $decoded['content'][$i];
682                    $tempClass = self::CLASS_UNIVERSAL;
683                    if (isset($temp['constant'])) {
684                        $tempClass = $temp['type'];
685                    }
686
687                    foreach ($mapping['children'] as $key => $child) {
688                        if (isset($map[$key])) {
689                            continue;
690                        }
691                        $maymatch = true;
692                        if ($child['type'] != self::TYPE_CHOICE) {
693                            $childClass = self::CLASS_UNIVERSAL;
694                            $constant = null;
695                            if (isset($child['class'])) {
696                                $childClass = $child['class'];
697                                $constant = $child['cast'];
698                            } elseif (isset($child['constant'])) {
699                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
700                                $constant = $child['constant'];
701                            }
702
703                            if (isset($constant) && isset($temp['constant'])) {
704                                // Can only match if constants and class match.
705                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
706                            } else {
707                                // Can only match if no constant expected and type matches or is generic.
708                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
709                            }
710                        }
711
712                        if ($maymatch) {
713                            // Attempt submapping.
714                            $candidate = $this->asn1map($temp, $child, $special);
715                            $maymatch = $candidate !== null;
716                        }
717
718                        if (!$maymatch) {
719                            break;
720                        }
721
722                        // Got the match: use it.
723                        if (isset($special[$key])) {
724                            $candidate = call_user_func($special[$key], $candidate);
725                        }
726                        $map[$key] = $candidate;
727                        break;
728                    }
729                }
730
731                foreach ($mapping['children'] as $key => $child) {
732                    if (!isset($map[$key])) {
733                        if (isset($child['default'])) {
734                            $map[$key] = $child['default'];
735                        } elseif (!isset($child['optional'])) {
736                            return null;
737                        }
738                    }
739                }
740                return $map;
741            case self::TYPE_OBJECT_IDENTIFIER:
742                return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
743            case self::TYPE_UTC_TIME:
744            case self::TYPE_GENERALIZED_TIME:
745                // for explicitly tagged optional stuff
746                if (is_array($decoded['content'])) {
747                    $decoded['content'] = $decoded['content'][0]['content'];
748                }
749                // for implicitly tagged optional stuff
750                // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
751                // in the wild that OpenSSL decodes without issue so we'll support them as well
752                if (!is_object($decoded['content'])) {
753                    $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
754                }
755                return $decoded['content'] ? $decoded['content']->format($this->format) : false;
756            case self::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 self::TYPE_OCTET_STRING:
786                return base64_encode($decoded['content']);
787            case self::TYPE_NULL:
788                return '';
789            case self::TYPE_BOOLEAN:
790                return $decoded['content'];
791            case self::TYPE_NUMERIC_STRING:
792            case self::TYPE_PRINTABLE_STRING:
793            case self::TYPE_TELETEX_STRING:
794            case self::TYPE_VIDEOTEX_STRING:
795            case self::TYPE_IA5_STRING:
796            case self::TYPE_GRAPHIC_STRING:
797            case self::TYPE_VISIBLE_STRING:
798            case self::TYPE_GENERAL_STRING:
799            case self::TYPE_UNIVERSAL_STRING:
800            case self::TYPE_UTF8_STRING:
801            case self::TYPE_BMP_STRING:
802                return $decoded['content'];
803            case self::TYPE_INTEGER:
804            case self::TYPE_ENUMERATED:
805                $temp = $decoded['content'];
806                if (isset($mapping['implicit'])) {
807                    $temp = new 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 array $special
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 int $idx
845     * @param array $special
846     * @return string
847     * @access private
848     */
849    function _encode_der($source, $mapping, $idx = null, $special = array())
850    {
851        if ($source instanceof Element) {
852            return $source->element;
853        }
854
855        // do not encode (implicitly optional) fields with value set to default
856        if (isset($mapping['default']) && $source === $mapping['default']) {
857            return '';
858        }
859
860        if (isset($idx)) {
861            if (isset($special[$idx])) {
862                $source = call_user_func($special[$idx], $source);
863            }
864            $this->location[] = $idx;
865        }
866
867        $tag = $mapping['type'];
868
869        switch ($tag) {
870            case self::TYPE_SET:    // Children order is not important, thus process in sequence.
871            case self::TYPE_SEQUENCE:
872                $tag|= 0x20; // set the constructed bit
873
874                // ignore the min and max
875                if (isset($mapping['min']) && isset($mapping['max'])) {
876                    $value = array();
877                    $child = $mapping['children'];
878
879                    foreach ($source as $content) {
880                        $temp = $this->_encode_der($content, $child, null, $special);
881                        if ($temp === false) {
882                            return false;
883                        }
884                        $value[]= $temp;
885                    }
886                    /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
887                        as octet strings with the shorter components being padded at their trailing end with 0-octets.
888                        NOTE - The padding octets are for comparison purposes only and do not appear in the encodings."
889
890                       -- sec 11.6 of http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf  */
891                    if ($mapping['type'] == self::TYPE_SET) {
892                        sort($value);
893                    }
894                    $value = implode('', $value);
895                    break;
896                }
897
898                $value = '';
899                foreach ($mapping['children'] as $key => $child) {
900                    if (!array_key_exists($key, $source)) {
901                        if (!isset($child['optional'])) {
902                            return false;
903                        }
904                        continue;
905                    }
906
907                    $temp = $this->_encode_der($source[$key], $child, $key, $special);
908                    if ($temp === false) {
909                        return false;
910                    }
911
912                    // An empty child encoding means it has been optimized out.
913                    // Else we should have at least one tag byte.
914                    if ($temp === '') {
915                        continue;
916                    }
917
918                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
919                    if (isset($child['constant'])) {
920                        /*
921                           From X.680-0207.pdf#page=58 (30.6):
922
923                           "The tagging construction specifies explicit tagging if any of the following holds:
924                            ...
925                            c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
926                            AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
927                            an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
928                         */
929                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
930                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
931                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
932                        } else {
933                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
934                            $temp = $subtag . substr($temp, 1);
935                        }
936                    }
937                    $value.= $temp;
938                }
939                break;
940            case self::TYPE_CHOICE:
941                $temp = false;
942
943                foreach ($mapping['children'] as $key => $child) {
944                    if (!isset($source[$key])) {
945                        continue;
946                    }
947
948                    $temp = $this->_encode_der($source[$key], $child, $key, $special);
949                    if ($temp === false) {
950                        return false;
951                    }
952
953                    // An empty child encoding means it has been optimized out.
954                    // Else we should have at least one tag byte.
955                    if ($temp === '') {
956                        continue;
957                    }
958
959                    $tag = ord($temp[0]);
960
961                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
962                    if (isset($child['constant'])) {
963                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
964                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
965                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
966                        } else {
967                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
968                            $temp = $subtag . substr($temp, 1);
969                        }
970                    }
971                }
972
973                if (isset($idx)) {
974                    array_pop($this->location);
975                }
976
977                if ($temp && isset($mapping['cast'])) {
978                    $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
979                }
980
981                return $temp;
982            case self::TYPE_INTEGER:
983            case self::TYPE_ENUMERATED:
984                if (!isset($mapping['mapping'])) {
985                    if (is_numeric($source)) {
986                        $source = new BigInteger($source);
987                    }
988                    $value = $source->toBytes(true);
989                } else {
990                    $value = array_search($source, $mapping['mapping']);
991                    if ($value === false) {
992                        return false;
993                    }
994                    $value = new BigInteger($value);
995                    $value = $value->toBytes(true);
996                }
997                if (!strlen($value)) {
998                    $value = chr(0);
999                }
1000                break;
1001            case self::TYPE_UTC_TIME:
1002            case self::TYPE_GENERALIZED_TIME:
1003                $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
1004                $format.= 'mdHis';
1005                // if $source does _not_ include timezone information within it then assume that the timezone is GMT
1006                $date = new DateTime($source, new DateTimeZone('GMT'));
1007                // if $source _does_ include timezone information within it then convert the time to GMT
1008                $date->setTimezone(new DateTimeZone('GMT'));
1009                $value = $date->format($format) . 'Z';
1010                break;
1011            case self::TYPE_BIT_STRING:
1012                if (isset($mapping['mapping'])) {
1013                    $bits = array_fill(0, count($mapping['mapping']), 0);
1014                    $size = 0;
1015                    for ($i = 0; $i < count($mapping['mapping']); $i++) {
1016                        if (in_array($mapping['mapping'][$i], $source)) {
1017                            $bits[$i] = 1;
1018                            $size = $i;
1019                        }
1020                    }
1021
1022                    if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
1023                        $size = $mapping['min'] - 1;
1024                    }
1025
1026                    $offset = 8 - (($size + 1) & 7);
1027                    $offset = $offset !== 8 ? $offset : 0;
1028
1029                    $value = chr($offset);
1030
1031                    for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
1032                        unset($bits[$i]);
1033                    }
1034
1035                    $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
1036                    $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
1037                    foreach ($bytes as $byte) {
1038                        $value.= chr(bindec($byte));
1039                    }
1040
1041                    break;
1042                }
1043            case self::TYPE_OCTET_STRING:
1044                /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
1045                   the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
1046
1047                   -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
1048                $value = base64_decode($source);
1049                break;
1050            case self::TYPE_OBJECT_IDENTIFIER:
1051                $value = $this->_encodeOID($source);
1052                break;
1053            case self::TYPE_ANY:
1054                $loc = $this->location;
1055                if (isset($idx)) {
1056                    array_pop($this->location);
1057                }
1058
1059                switch (true) {
1060                    case !isset($source):
1061                        return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special);
1062                    case is_int($source):
1063                    case $source instanceof BigInteger:
1064                        return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special);
1065                    case is_float($source):
1066                        return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special);
1067                    case is_bool($source):
1068                        return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special);
1069                    case is_array($source) && count($source) == 1:
1070                        $typename = implode('', array_keys($source));
1071                        $outtype = array_search($typename, $this->ANYmap, true);
1072                        if ($outtype !== false) {
1073                            return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
1074                        }
1075                }
1076
1077                $filters = $this->filters;
1078                foreach ($loc as $part) {
1079                    if (!isset($filters[$part])) {
1080                        $filters = false;
1081                        break;
1082                    }
1083                    $filters = $filters[$part];
1084                }
1085                if ($filters === false) {
1086                    user_error('No filters defined for ' . implode('/', $loc));
1087                    return false;
1088                }
1089                return $this->_encode_der($source, $filters + $mapping, null, $special);
1090            case self::TYPE_NULL:
1091                $value = '';
1092                break;
1093            case self::TYPE_NUMERIC_STRING:
1094            case self::TYPE_TELETEX_STRING:
1095            case self::TYPE_PRINTABLE_STRING:
1096            case self::TYPE_UNIVERSAL_STRING:
1097            case self::TYPE_UTF8_STRING:
1098            case self::TYPE_BMP_STRING:
1099            case self::TYPE_IA5_STRING:
1100            case self::TYPE_VISIBLE_STRING:
1101            case self::TYPE_VIDEOTEX_STRING:
1102            case self::TYPE_GRAPHIC_STRING:
1103            case self::TYPE_GENERAL_STRING:
1104                $value = $source;
1105                break;
1106            case self::TYPE_BOOLEAN:
1107                $value = $source ? "\xFF" : "\x00";
1108                break;
1109            default:
1110                user_error('Mapping provides no type definition for ' . implode('/', $this->location));
1111                return false;
1112        }
1113
1114        if (isset($idx)) {
1115            array_pop($this->location);
1116        }
1117
1118        if (isset($mapping['cast'])) {
1119            if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
1120                $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1121                $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
1122            } else {
1123                $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
1124            }
1125        }
1126
1127        return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1128    }
1129
1130    /**
1131     * DER-encode the length
1132     *
1133     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
1134     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
1135     *
1136     * @access private
1137     * @param int $length
1138     * @return string
1139     */
1140    function _encodeLength($length)
1141    {
1142        if ($length <= 0x7F) {
1143            return chr($length);
1144        }
1145
1146        $temp = ltrim(pack('N', $length), chr(0));
1147        return pack('Ca*', 0x80 | strlen($temp), $temp);
1148    }
1149
1150    /**
1151     * BER-decode the OID
1152     *
1153     * Called by _decode_ber()
1154     *
1155     * @access private
1156     * @param string $content
1157     * @return string
1158     */
1159    function _decodeOID($content)
1160    {
1161        static $eighty;
1162        if (!$eighty) {
1163            $eighty = new BigInteger(80);
1164        }
1165
1166        $oid = array();
1167        $pos = 0;
1168        $len = strlen($content);
1169
1170        if (ord($content[$len - 1]) & 0x80) {
1171            return false;
1172        }
1173
1174        $n = new BigInteger();
1175        while ($pos < $len) {
1176            $temp = ord($content[$pos++]);
1177            $n = $n->bitwise_leftShift(7);
1178            $n = $n->bitwise_or(new BigInteger($temp & 0x7F));
1179            if (~$temp & 0x80) {
1180                $oid[] = $n;
1181                $n = new BigInteger();
1182            }
1183        }
1184        $part1 = array_shift($oid);
1185        $first = floor(ord($content[0]) / 40);
1186        /*
1187          "This packing of the first two object identifier components recognizes that only three values are allocated from the root
1188           node, and at most 39 subsequent values from nodes reached by X = 0 and X = 1."
1189
1190          -- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22
1191        */
1192        if ($first <= 2) { // ie. 0 <= ord($content[0]) < 120 (0x78)
1193            array_unshift($oid, ord($content[0]) % 40);
1194            array_unshift($oid, $first);
1195        } else {
1196            array_unshift($oid, $part1->subtract($eighty));
1197            array_unshift($oid, 2);
1198        }
1199
1200        return implode('.', $oid);
1201    }
1202
1203    /**
1204     * DER-encode the OID
1205     *
1206     * Called by _encode_der()
1207     *
1208     * @access private
1209     * @param string $source
1210     * @return string
1211     */
1212    function _encodeOID($source)
1213    {
1214        static $mask, $zero, $forty;
1215        if (!$mask) {
1216            $mask = new BigInteger(0x7F);
1217            $zero = new BigInteger();
1218            $forty = new BigInteger(40);
1219        }
1220
1221        $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
1222        if ($oid === false) {
1223            user_error('Invalid OID');
1224            return false;
1225        }
1226        $parts = explode('.', $oid);
1227        $part1 = array_shift($parts);
1228        $part2 = array_shift($parts);
1229
1230        $first = new BigInteger($part1);
1231        $first = $first->multiply($forty);
1232        $first = $first->add(new BigInteger($part2));
1233
1234        array_unshift($parts, $first->toString());
1235
1236        $value = '';
1237        foreach ($parts as $part) {
1238            if (!$part) {
1239                $temp = "\0";
1240            } else {
1241                $temp = '';
1242                $part = new BigInteger($part);
1243                while (!$part->equals($zero)) {
1244                    $submask = $part->bitwise_and($mask);
1245                    $submask->setPrecision(8);
1246                    $temp = (chr(0x80) | $submask->toBytes()) . $temp;
1247                    $part = $part->bitwise_rightShift(7);
1248                }
1249                $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
1250            }
1251            $value.= $temp;
1252        }
1253
1254        return $value;
1255    }
1256
1257    /**
1258     * BER-decode the time
1259     *
1260     * Called by _decode_ber() and in the case of implicit tags asn1map().
1261     *
1262     * @access private
1263     * @param string $content
1264     * @param int $tag
1265     * @return string
1266     */
1267    function _decodeTime($content, $tag)
1268    {
1269        /* UTCTime:
1270           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1271           http://www.obj-sys.com/asn1tutorial/node15.html
1272
1273           GeneralizedTime:
1274           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1275           http://www.obj-sys.com/asn1tutorial/node14.html */
1276
1277        $format = 'YmdHis';
1278
1279        if ($tag == self::TYPE_UTC_TIME) {
1280            // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds
1281            // element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the
1282            // browsers parse it phpseclib ought to too
1283            if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) {
1284                $content = $matches[1] . '00' . $matches[2];
1285            }
1286            $prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
1287            $content = $prefix . $content;
1288        } elseif (strpos($content, '.') !== false) {
1289            $format.= '.u';
1290        }
1291
1292        if ($content[strlen($content) - 1] == 'Z') {
1293            $content = substr($content, 0, -1) . '+0000';
1294        }
1295
1296        if (strpos($content, '-') !== false || strpos($content, '+') !== false) {
1297            $format.= 'O';
1298        }
1299
1300        // error supression isn't necessary as of PHP 7.0:
1301        // http://php.net/manual/en/migration70.other-changes.php
1302        return @DateTime::createFromFormat($format, $content);
1303    }
1304
1305    /**
1306     * Set the time format
1307     *
1308     * Sets the time / date format for asn1map().
1309     *
1310     * @access public
1311     * @param string $format
1312     */
1313    function setTimeFormat($format)
1314    {
1315        $this->format = $format;
1316    }
1317
1318    /**
1319     * Load OIDs
1320     *
1321     * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1322     *
1323     * @access public
1324     * @param array $oids
1325     */
1326    function loadOIDs($oids)
1327    {
1328        $this->oids = $oids;
1329    }
1330
1331    /**
1332     * Load filters
1333     *
1334     * See \phpseclib\File\X509, etc, for an example.
1335     *
1336     * @access public
1337     * @param array $filters
1338     */
1339    function loadFilters($filters)
1340    {
1341        $this->filters = $filters;
1342    }
1343
1344    /**
1345     * String Shift
1346     *
1347     * Inspired by array_shift
1348     *
1349     * @param string $string
1350     * @param int $index
1351     * @return string
1352     * @access private
1353     */
1354    function _string_shift(&$string, $index = 1)
1355    {
1356        $substr = substr($string, 0, $index);
1357        $string = substr($string, $index);
1358        return $substr;
1359    }
1360
1361    /**
1362     * String type conversion
1363     *
1364     * This is a lazy conversion, dealing only with character size.
1365     * No real conversion table is used.
1366     *
1367     * @param string $in
1368     * @param int $from
1369     * @param int $to
1370     * @return string
1371     * @access public
1372     */
1373    function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
1374    {
1375        if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1376            return false;
1377        }
1378        $insize = $this->stringTypeSize[$from];
1379        $outsize = $this->stringTypeSize[$to];
1380        $inlength = strlen($in);
1381        $out = '';
1382
1383        for ($i = 0; $i < $inlength;) {
1384            if ($inlength - $i < $insize) {
1385                return false;
1386            }
1387
1388            // Get an input character as a 32-bit value.
1389            $c = ord($in[$i++]);
1390            switch (true) {
1391                case $insize == 4:
1392                    $c = ($c << 8) | ord($in[$i++]);
1393                    $c = ($c << 8) | ord($in[$i++]);
1394                case $insize == 2:
1395                    $c = ($c << 8) | ord($in[$i++]);
1396                case $insize == 1:
1397                    break;
1398                case ($c & 0x80) == 0x00:
1399                    break;
1400                case ($c & 0x40) == 0x00:
1401                    return false;
1402                default:
1403                    $bit = 6;
1404                    do {
1405                        if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1406                            return false;
1407                        }
1408                        $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1409                        $bit += 5;
1410                        $mask = 1 << $bit;
1411                    } while ($c & $bit);
1412                    $c &= $mask - 1;
1413                    break;
1414            }
1415
1416            // Convert and append the character to output string.
1417            $v = '';
1418            switch (true) {
1419                case $outsize == 4:
1420                    $v .= chr($c & 0xFF);
1421                    $c >>= 8;
1422                    $v .= chr($c & 0xFF);
1423                    $c >>= 8;
1424                case $outsize == 2:
1425                    $v .= chr($c & 0xFF);
1426                    $c >>= 8;
1427                case $outsize == 1:
1428                    $v .= chr($c & 0xFF);
1429                    $c >>= 8;
1430                    if ($c) {
1431                        return false;
1432                    }
1433                    break;
1434                case ($c & 0x80000000) != 0:
1435                    return false;
1436                case $c >= 0x04000000:
1437                    $v .= chr(0x80 | ($c & 0x3F));
1438                    $c = ($c >> 6) | 0x04000000;
1439                case $c >= 0x00200000:
1440                    $v .= chr(0x80 | ($c & 0x3F));
1441                    $c = ($c >> 6) | 0x00200000;
1442                case $c >= 0x00010000:
1443                    $v .= chr(0x80 | ($c & 0x3F));
1444                    $c = ($c >> 6) | 0x00010000;
1445                case $c >= 0x00000800:
1446                    $v .= chr(0x80 | ($c & 0x3F));
1447                    $c = ($c >> 6) | 0x00000800;
1448                case $c >= 0x00000080:
1449                    $v .= chr(0x80 | ($c & 0x3F));
1450                    $c = ($c >> 6) | 0x000000C0;
1451                default:
1452                    $v .= chr($c);
1453                    break;
1454            }
1455            $out .= strrev($v);
1456        }
1457        return $out;
1458    }
1459}
1460