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