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