[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

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


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer