PageRenderTime 56ms CodeModel.GetById 16ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/phpseclib/File/ASN1.php

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

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