PageRenderTime 66ms CodeModel.GetById 28ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/src/PHPSecLib/File/ASN1.php

https://github.com/dsawardekar/worker
PHP | 1300 lines | 797 code | 113 blank | 390 comment | 174 complexity | e1c4cf445a8251da3c1289524a101654 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file