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