[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/File/ -> ASN1.php (source)

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