[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/phpseclib/phpseclib/phpseclib/File/ -> X509.php (source)

   1  <?php
   2  
   3  /**
   4   * Pure-PHP X.509 Parser
   5   *
   6   * PHP version 5
   7   *
   8   * Encode and decode X.509 certificates.
   9   *
  10   * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
  11   * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
  12   *
  13   * Note that loading an X.509 certificate and resaving it may invalidate the signature.  The reason being that the signature is based on a
  14   * portion of the certificate that contains optional parameters with default values.  ie. if the parameter isn't there the default value is
  15   * used.  Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
  16   * be encoded.  It can be encoded explicitly or left out all together.  This would effect the signature value and thus may invalidate the
  17   * the certificate all together unless the certificate is re-signed.
  18   *
  19   * @category  File
  20   * @package   X509
  21   * @author    Jim Wigginton <[email protected]>
  22   * @copyright 2012 Jim Wigginton
  23   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  24   * @link      http://phpseclib.sourceforge.net
  25   */
  26  
  27  namespace phpseclib3\File;
  28  
  29  use ParagonIE\ConstantTime\Base64;
  30  use ParagonIE\ConstantTime\Hex;
  31  use phpseclib3\Crypt\Common\PrivateKey;
  32  use phpseclib3\Crypt\Common\PublicKey;
  33  use phpseclib3\Crypt\DSA;
  34  use phpseclib3\Crypt\EC;
  35  use phpseclib3\Crypt\Hash;
  36  use phpseclib3\Crypt\PublicKeyLoader;
  37  use phpseclib3\Crypt\Random;
  38  use phpseclib3\Crypt\RSA;
  39  use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
  40  use phpseclib3\Exception\UnsupportedAlgorithmException;
  41  use phpseclib3\File\ASN1\Element;
  42  use phpseclib3\File\ASN1\Maps;
  43  use phpseclib3\Math\BigInteger;
  44  
  45  /**
  46   * Pure-PHP X.509 Parser
  47   *
  48   * @package X509
  49   * @author  Jim Wigginton <[email protected]>
  50   * @access  public
  51   */
  52  class X509
  53  {
  54      /**
  55       * Flag to only accept signatures signed by certificate authorities
  56       *
  57       * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
  58       *
  59       * @access public
  60       */
  61      const VALIDATE_SIGNATURE_BY_CA = 1;
  62  
  63      /**
  64       * Return internal array representation
  65       *
  66       * @access public
  67       * @see \phpseclib3\File\X509::getDN()
  68       */
  69      const DN_ARRAY = 0;
  70      /**
  71       * Return string
  72       *
  73       * @access public
  74       * @see \phpseclib3\File\X509::getDN()
  75       */
  76      const DN_STRING = 1;
  77      /**
  78       * Return ASN.1 name string
  79       *
  80       * @access public
  81       * @see \phpseclib3\File\X509::getDN()
  82       */
  83      const DN_ASN1 = 2;
  84      /**
  85       * Return OpenSSL compatible array
  86       *
  87       * @access public
  88       * @see \phpseclib3\File\X509::getDN()
  89       */
  90      const DN_OPENSSL = 3;
  91      /**
  92       * Return canonical ASN.1 RDNs string
  93       *
  94       * @access public
  95       * @see \phpseclib3\File\X509::getDN()
  96       */
  97      const DN_CANON = 4;
  98      /**
  99       * Return name hash for file indexing
 100       *
 101       * @access public
 102       * @see \phpseclib3\File\X509::getDN()
 103       */
 104      const DN_HASH = 5;
 105  
 106      /**
 107       * Save as PEM
 108       *
 109       * ie. a base64-encoded PEM with a header and a footer
 110       *
 111       * @access public
 112       * @see \phpseclib3\File\X509::saveX509()
 113       * @see \phpseclib3\File\X509::saveCSR()
 114       * @see \phpseclib3\File\X509::saveCRL()
 115       */
 116      const FORMAT_PEM = 0;
 117      /**
 118       * Save as DER
 119       *
 120       * @access public
 121       * @see \phpseclib3\File\X509::saveX509()
 122       * @see \phpseclib3\File\X509::saveCSR()
 123       * @see \phpseclib3\File\X509::saveCRL()
 124       */
 125      const FORMAT_DER = 1;
 126      /**
 127       * Save as a SPKAC
 128       *
 129       * @access public
 130       * @see \phpseclib3\File\X509::saveX509()
 131       * @see \phpseclib3\File\X509::saveCSR()
 132       * @see \phpseclib3\File\X509::saveCRL()
 133       *
 134       * Only works on CSRs. Not currently supported.
 135       */
 136      const FORMAT_SPKAC = 2;
 137      /**
 138       * Auto-detect the format
 139       *
 140       * Used only by the load*() functions
 141       *
 142       * @access public
 143       * @see \phpseclib3\File\X509::saveX509()
 144       * @see \phpseclib3\File\X509::saveCSR()
 145       * @see \phpseclib3\File\X509::saveCRL()
 146       */
 147      const FORMAT_AUTO_DETECT = 3;
 148  
 149      /**
 150       * Attribute value disposition.
 151       * If disposition is >= 0, this is the index of the target value.
 152       */
 153      const ATTR_ALL = -1; // All attribute values (array).
 154      const ATTR_APPEND = -2; // Add a value.
 155      const ATTR_REPLACE = -3; // Clear first, then add a value.
 156  
 157      /**
 158       * Distinguished Name
 159       *
 160       * @var array
 161       * @access private
 162       */
 163      private $dn;
 164  
 165      /**
 166       * Public key
 167       *
 168       * @var string|PublicKey
 169       * @access private
 170       */
 171      private $publicKey;
 172  
 173      /**
 174       * Private key
 175       *
 176       * @var string|PrivateKey
 177       * @access private
 178       */
 179      private $privateKey;
 180  
 181      /**
 182       * Object identifiers for X.509 certificates
 183       *
 184       * @var array
 185       * @access private
 186       * @link http://en.wikipedia.org/wiki/Object_identifier
 187       */
 188      private $oids;
 189  
 190      /**
 191       * The certificate authorities
 192       *
 193       * @var array
 194       * @access private
 195       */
 196      private $CAs;
 197  
 198      /**
 199       * The currently loaded certificate
 200       *
 201       * @var array
 202       * @access private
 203       */
 204      private $currentCert;
 205  
 206      /**
 207       * The signature subject
 208       *
 209       * There's no guarantee \phpseclib3\File\X509 is going to re-encode an X.509 cert in the same way it was originally
 210       * encoded so we take save the portion of the original cert that the signature would have made for.
 211       *
 212       * @var string
 213       * @access private
 214       */
 215      private $signatureSubject;
 216  
 217      /**
 218       * Certificate Start Date
 219       *
 220       * @var string
 221       * @access private
 222       */
 223      private $startDate;
 224  
 225      /**
 226       * Certificate End Date
 227       *
 228       * @var string|Element
 229       * @access private
 230       */
 231      private $endDate;
 232  
 233      /**
 234       * Serial Number
 235       *
 236       * @var string
 237       * @access private
 238       */
 239      private $serialNumber;
 240  
 241      /**
 242       * Key Identifier
 243       *
 244       * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
 245       * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
 246       *
 247       * @var string
 248       * @access private
 249       */
 250      private $currentKeyIdentifier;
 251  
 252      /**
 253       * CA Flag
 254       *
 255       * @var bool
 256       * @access private
 257       */
 258      private $caFlag = false;
 259  
 260      /**
 261       * SPKAC Challenge
 262       *
 263       * @var string
 264       * @access private
 265       */
 266      private $challenge;
 267  
 268      /**
 269       * @var array
 270       * @access private
 271       */
 272      private $extensionValues = [];
 273  
 274      /**
 275       * OIDs loaded
 276       *
 277       * @var bool
 278       * @access private
 279       */
 280      private static $oidsLoaded = false;
 281  
 282      /**
 283       * Recursion Limit
 284       *
 285       * @var int
 286       * @access private
 287       */
 288      private static $recur_limit = 5;
 289  
 290      /**
 291       * URL fetch flag
 292       *
 293       * @var bool
 294       * @access private
 295       */
 296      private static $disable_url_fetch = false;
 297  
 298      /**
 299       * @var array
 300       * @access private
 301       */
 302      private static $extensions = [];
 303  
 304      /**
 305       * @var ?array
 306       * @access private
 307       */
 308      private $ipAddresses = null;
 309  
 310      /**
 311       * @var ?array
 312       * @access private
 313       */
 314      private $domains = null;
 315  
 316      /**
 317       * Default Constructor.
 318       *
 319       * @return \phpseclib3\File\X509
 320       * @access public
 321       */
 322      public function __construct()
 323      {
 324          // Explicitly Tagged Module, 1988 Syntax
 325          // http://tools.ietf.org/html/rfc5280#appendix-A.1
 326  
 327          if (!self::$oidsLoaded) {
 328              // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
 329              ASN1::loadOIDs([
 330                  //'id-pkix' => '1.3.6.1.5.5.7',
 331                  //'id-pe' => '1.3.6.1.5.5.7.1',
 332                  //'id-qt' => '1.3.6.1.5.5.7.2',
 333                  //'id-kp' => '1.3.6.1.5.5.7.3',
 334                  //'id-ad' => '1.3.6.1.5.5.7.48',
 335                  'id-qt-cps' => '1.3.6.1.5.5.7.2.1',
 336                  'id-qt-unotice' => '1.3.6.1.5.5.7.2.2',
 337                  'id-ad-ocsp' => '1.3.6.1.5.5.7.48.1',
 338                  'id-ad-caIssuers' => '1.3.6.1.5.5.7.48.2',
 339                  'id-ad-timeStamping' => '1.3.6.1.5.5.7.48.3',
 340                  'id-ad-caRepository' => '1.3.6.1.5.5.7.48.5',
 341                  //'id-at' => '2.5.4',
 342                  'id-at-name' => '2.5.4.41',
 343                  'id-at-surname' => '2.5.4.4',
 344                  'id-at-givenName' => '2.5.4.42',
 345                  'id-at-initials' => '2.5.4.43',
 346                  'id-at-generationQualifier' => '2.5.4.44',
 347                  'id-at-commonName' => '2.5.4.3',
 348                  'id-at-localityName' => '2.5.4.7',
 349                  'id-at-stateOrProvinceName' => '2.5.4.8',
 350                  'id-at-organizationName' => '2.5.4.10',
 351                  'id-at-organizationalUnitName' => '2.5.4.11',
 352                  'id-at-title' => '2.5.4.12',
 353                  'id-at-description' => '2.5.4.13',
 354                  'id-at-dnQualifier' => '2.5.4.46',
 355                  'id-at-countryName' => '2.5.4.6',
 356                  'id-at-serialNumber' => '2.5.4.5',
 357                  'id-at-pseudonym' => '2.5.4.65',
 358                  'id-at-postalCode' => '2.5.4.17',
 359                  'id-at-streetAddress' => '2.5.4.9',
 360                  'id-at-uniqueIdentifier' => '2.5.4.45',
 361                  'id-at-role' => '2.5.4.72',
 362                  'id-at-postalAddress' => '2.5.4.16',
 363  
 364                  //'id-domainComponent' => '0.9.2342.19200300.100.1.25',
 365                  //'pkcs-9' => '1.2.840.113549.1.9',
 366                  'pkcs-9-at-emailAddress' => '1.2.840.113549.1.9.1',
 367                  //'id-ce' => '2.5.29',
 368                  'id-ce-authorityKeyIdentifier' => '2.5.29.35',
 369                  'id-ce-subjectKeyIdentifier' => '2.5.29.14',
 370                  'id-ce-keyUsage' => '2.5.29.15',
 371                  'id-ce-privateKeyUsagePeriod' => '2.5.29.16',
 372                  'id-ce-certificatePolicies' => '2.5.29.32',
 373                  //'anyPolicy' => '2.5.29.32.0',
 374  
 375                  'id-ce-policyMappings' => '2.5.29.33',
 376  
 377                  'id-ce-subjectAltName' => '2.5.29.17',
 378                  'id-ce-issuerAltName' => '2.5.29.18',
 379                  'id-ce-subjectDirectoryAttributes' => '2.5.29.9',
 380                  'id-ce-basicConstraints' => '2.5.29.19',
 381                  'id-ce-nameConstraints' => '2.5.29.30',
 382                  'id-ce-policyConstraints' => '2.5.29.36',
 383                  'id-ce-cRLDistributionPoints' => '2.5.29.31',
 384                  'id-ce-extKeyUsage' => '2.5.29.37',
 385                  //'anyExtendedKeyUsage' => '2.5.29.37.0',
 386                  'id-kp-serverAuth' => '1.3.6.1.5.5.7.3.1',
 387                  'id-kp-clientAuth' => '1.3.6.1.5.5.7.3.2',
 388                  'id-kp-codeSigning' => '1.3.6.1.5.5.7.3.3',
 389                  'id-kp-emailProtection' => '1.3.6.1.5.5.7.3.4',
 390                  'id-kp-timeStamping' => '1.3.6.1.5.5.7.3.8',
 391                  'id-kp-OCSPSigning' => '1.3.6.1.5.5.7.3.9',
 392                  'id-ce-inhibitAnyPolicy' => '2.5.29.54',
 393                  'id-ce-freshestCRL' => '2.5.29.46',
 394                  'id-pe-authorityInfoAccess' => '1.3.6.1.5.5.7.1.1',
 395                  'id-pe-subjectInfoAccess' => '1.3.6.1.5.5.7.1.11',
 396                  'id-ce-cRLNumber' => '2.5.29.20',
 397                  'id-ce-issuingDistributionPoint' => '2.5.29.28',
 398                  'id-ce-deltaCRLIndicator' => '2.5.29.27',
 399                  'id-ce-cRLReasons' => '2.5.29.21',
 400                  'id-ce-certificateIssuer' => '2.5.29.29',
 401                  'id-ce-holdInstructionCode' => '2.5.29.23',
 402                  //'holdInstruction' => '1.2.840.10040.2',
 403                  'id-holdinstruction-none' => '1.2.840.10040.2.1',
 404                  'id-holdinstruction-callissuer' => '1.2.840.10040.2.2',
 405                  'id-holdinstruction-reject' => '1.2.840.10040.2.3',
 406                  'id-ce-invalidityDate' => '2.5.29.24',
 407  
 408                  'rsaEncryption' => '1.2.840.113549.1.1.1',
 409                  'md2WithRSAEncryption' => '1.2.840.113549.1.1.2',
 410                  'md5WithRSAEncryption' => '1.2.840.113549.1.1.4',
 411                  'sha1WithRSAEncryption' => '1.2.840.113549.1.1.5',
 412                  'sha224WithRSAEncryption' => '1.2.840.113549.1.1.14',
 413                  'sha256WithRSAEncryption' => '1.2.840.113549.1.1.11',
 414                  'sha384WithRSAEncryption' => '1.2.840.113549.1.1.12',
 415                  'sha512WithRSAEncryption' => '1.2.840.113549.1.1.13',
 416  
 417                  'id-ecPublicKey' => '1.2.840.10045.2.1',
 418                  'ecdsa-with-SHA1' => '1.2.840.10045.4.1',
 419                  // from https://tools.ietf.org/html/rfc5758#section-3.2
 420                  'ecdsa-with-SHA224' => '1.2.840.10045.4.3.1',
 421                  'ecdsa-with-SHA256' => '1.2.840.10045.4.3.2',
 422                  'ecdsa-with-SHA384' => '1.2.840.10045.4.3.3',
 423                  'ecdsa-with-SHA512' => '1.2.840.10045.4.3.4',
 424  
 425                  'id-dsa' => '1.2.840.10040.4.1',
 426                  'id-dsa-with-sha1' => '1.2.840.10040.4.3',
 427                  // from https://tools.ietf.org/html/rfc5758#section-3.1
 428                  'id-dsa-with-sha224' => '2.16.840.1.101.3.4.3.1',
 429                  'id-dsa-with-sha256' => '2.16.840.1.101.3.4.3.2',
 430  
 431                  // from https://tools.ietf.org/html/rfc8410:
 432                  'id-Ed25519' => '1.3.101.112',
 433                  'id-Ed448' => '1.3.101.113',
 434  
 435                  'id-RSASSA-PSS' => '1.2.840.113549.1.1.10',
 436  
 437                  //'id-sha224' => '2.16.840.1.101.3.4.2.4',
 438                  //'id-sha256' => '2.16.840.1.101.3.4.2.1',
 439                  //'id-sha384' => '2.16.840.1.101.3.4.2.2',
 440                  //'id-sha512' => '2.16.840.1.101.3.4.2.3',
 441                  //'id-GostR3411-94-with-GostR3410-94' => '1.2.643.2.2.4',
 442                  //'id-GostR3411-94-with-GostR3410-2001' => '1.2.643.2.2.3',
 443                  //'id-GostR3410-2001' => '1.2.643.2.2.20',
 444                  //'id-GostR3410-94' => '1.2.643.2.2.19',
 445                  // Netscape Object Identifiers from "Netscape Certificate Extensions"
 446                  'netscape' => '2.16.840.1.113730',
 447                  'netscape-cert-extension' => '2.16.840.1.113730.1',
 448                  'netscape-cert-type' => '2.16.840.1.113730.1.1',
 449                  'netscape-comment' => '2.16.840.1.113730.1.13',
 450                  'netscape-ca-policy-url' => '2.16.840.1.113730.1.8',
 451                  // the following are X.509 extensions not supported by phpseclib
 452                  'id-pe-logotype' => '1.3.6.1.5.5.7.1.12',
 453                  'entrustVersInfo' => '1.2.840.113533.7.65.0',
 454                  'verisignPrivate' => '2.16.840.1.113733.1.6.9',
 455                  // for Certificate Signing Requests
 456                  // see http://tools.ietf.org/html/rfc2985
 457                  'pkcs-9-at-unstructuredName' => '1.2.840.113549.1.9.2', // PKCS #9 unstructured name
 458                  'pkcs-9-at-challengePassword' => '1.2.840.113549.1.9.7', // Challenge password for certificate revocations
 459                  'pkcs-9-at-extensionRequest' => '1.2.840.113549.1.9.14' // Certificate extension request
 460              ]);
 461          }
 462      }
 463  
 464      /**
 465       * Load X.509 certificate
 466       *
 467       * Returns an associative array describing the X.509 cert or a false if the cert failed to load
 468       *
 469       * @param string $cert
 470       * @param int $mode
 471       * @access public
 472       * @return mixed
 473       */
 474      public function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT)
 475      {
 476          if (is_array($cert) && isset($cert['tbsCertificate'])) {
 477              unset($this->currentCert);
 478              unset($this->currentKeyIdentifier);
 479              $this->dn = $cert['tbsCertificate']['subject'];
 480              if (!isset($this->dn)) {
 481                  return false;
 482              }
 483              $this->currentCert = $cert;
 484  
 485              $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
 486              $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
 487  
 488              unset($this->signatureSubject);
 489  
 490              return $cert;
 491          }
 492  
 493          if ($mode != self::FORMAT_DER) {
 494              $newcert = ASN1::extractBER($cert);
 495              if ($mode == self::FORMAT_PEM && $cert == $newcert) {
 496                  return false;
 497              }
 498              $cert = $newcert;
 499          }
 500  
 501          if ($cert === false) {
 502              $this->currentCert = false;
 503              return false;
 504          }
 505  
 506          $decoded = ASN1::decodeBER($cert);
 507  
 508          if (!empty($decoded)) {
 509              $x509 = ASN1::asn1map($decoded[0], Maps\Certificate::MAP);
 510          }
 511          if (!isset($x509) || $x509 === false) {
 512              $this->currentCert = false;
 513              return false;
 514          }
 515  
 516          $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
 517  
 518          if ($this->isSubArrayValid($x509, 'tbsCertificate/extensions')) {
 519              $this->mapInExtensions($x509, 'tbsCertificate/extensions');
 520          }
 521          $this->mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence');
 522          $this->mapInDNs($x509, 'tbsCertificate/subject/rdnSequence');
 523  
 524          $key = $x509['tbsCertificate']['subjectPublicKeyInfo'];
 525          $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP);
 526          $x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] =
 527              "-----BEGIN PUBLIC KEY-----\r\n" .
 528              chunk_split(base64_encode($key), 64) .
 529              "-----END PUBLIC KEY-----";
 530  
 531          $this->currentCert = $x509;
 532          $this->dn = $x509['tbsCertificate']['subject'];
 533  
 534          $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
 535          $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
 536  
 537          return $x509;
 538      }
 539  
 540      /**
 541       * Save X.509 certificate
 542       *
 543       * @param array $cert
 544       * @param int $format optional
 545       * @access public
 546       * @return string
 547       */
 548      public function saveX509($cert, $format = self::FORMAT_PEM)
 549      {
 550          if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
 551              return false;
 552          }
 553  
 554          switch (true) {
 555              // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
 556              case !($algorithm = $this->subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
 557              case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
 558                  break;
 559              default:
 560                  $cert['tbsCertificate']['subjectPublicKeyInfo'] = new Element(
 561                      base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))
 562                  );
 563          }
 564  
 565          if ($algorithm == 'rsaEncryption') {
 566              $cert['signatureAlgorithm']['parameters'] = null;
 567              $cert['tbsCertificate']['signature']['parameters'] = null;
 568          }
 569  
 570          $filters = [];
 571          $type_utf8_string = ['type' => ASN1::TYPE_UTF8_STRING];
 572          $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
 573          $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
 574          $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
 575          $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string;
 576          $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string;
 577          $filters['signatureAlgorithm']['parameters'] = $type_utf8_string;
 578          $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
 579          //$filters['policyQualifiers']['qualifier'] = $type_utf8_string;
 580          $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
 581          $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;
 582  
 583          foreach (self::$extensions as $extension) {
 584              $filters['tbsCertificate']['extensions'][] = $extension;
 585          }
 586  
 587          /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib3\File\ASN1::TYPE_IA5_STRING.
 588             \phpseclib3\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
 589             characters.
 590           */
 591          $filters['policyQualifiers']['qualifier']
 592              = ['type' => ASN1::TYPE_IA5_STRING];
 593  
 594          ASN1::setFilters($filters);
 595  
 596          $this->mapOutExtensions($cert, 'tbsCertificate/extensions');
 597          $this->mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence');
 598          $this->mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence');
 599  
 600          $cert = ASN1::encodeDER($cert, Maps\Certificate::MAP);
 601  
 602          switch ($format) {
 603              case self::FORMAT_DER:
 604                  return $cert;
 605              // case self::FORMAT_PEM:
 606              default:
 607                  return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(Base64::encode($cert), 64) . '-----END CERTIFICATE-----';
 608          }
 609      }
 610  
 611      /**
 612       * Map extension values from octet string to extension-specific internal
 613       *   format.
 614       *
 615       * @param array $root (by reference)
 616       * @param string $path
 617       * @access private
 618       */
 619      private function mapInExtensions(&$root, $path)
 620      {
 621          $extensions = &$this->subArrayUnchecked($root, $path);
 622  
 623          if ($extensions) {
 624              for ($i = 0; $i < count($extensions); $i++) {
 625                  $id = $extensions[$i]['extnId'];
 626                  $value = &$extensions[$i]['extnValue'];
 627                  /* [extnValue] contains the DER encoding of an ASN.1 value
 628                     corresponding to the extension type identified by extnID */
 629                  $map = $this->getMapping($id);
 630                  if (!is_bool($map)) {
 631                      $decoder = $id == 'id-ce-nameConstraints' ?
 632                          [static::class, 'decodeNameConstraintIP'] :
 633                          [static::class, 'decodeIP'];
 634                      $decoded = ASN1::decodeBER($value);
 635                      $mapped = ASN1::asn1map($decoded[0], $map, ['iPAddress' => $decoder]);
 636                      $value = $mapped === false ? $decoded[0] : $mapped;
 637  
 638                      if ($id == 'id-ce-certificatePolicies') {
 639                          for ($j = 0; $j < count($value); $j++) {
 640                              if (!isset($value[$j]['policyQualifiers'])) {
 641                                  continue;
 642                              }
 643                              for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
 644                                  $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
 645                                  $map = $this->getMapping($subid);
 646                                  $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
 647                                  if ($map !== false) {
 648                                      $decoded = ASN1::decodeBER($subvalue);
 649                                      $mapped = ASN1::asn1map($decoded[0], $map);
 650                                      $subvalue = $mapped === false ? $decoded[0] : $mapped;
 651                                  }
 652                              }
 653                          }
 654                      }
 655                  }
 656              }
 657          }
 658      }
 659  
 660      /**
 661       * Map extension values from extension-specific internal format to
 662       *   octet string.
 663       *
 664       * @param array $root (by reference)
 665       * @param string $path
 666       * @access private
 667       */
 668      private function mapOutExtensions(&$root, $path)
 669      {
 670          $extensions = &$this->subArray($root, $path, !empty($this->extensionValues));
 671  
 672          foreach ($this->extensionValues as $id => $data) {
 673              extract($data);
 674              $newext = [
 675                  'extnId' => $id,
 676                  'extnValue' => $value,
 677                  'critical' => $critical
 678              ];
 679              if ($replace) {
 680                  foreach ($extensions as $key => $value) {
 681                      if ($value['extnId'] == $id) {
 682                          $extensions[$key] = $newext;
 683                          continue 2;
 684                      }
 685                  }
 686              }
 687              $extensions[] = $newext;
 688          }
 689  
 690          if (is_array($extensions)) {
 691              $size = count($extensions);
 692              for ($i = 0; $i < $size; $i++) {
 693                  if ($extensions[$i] instanceof Element) {
 694                      continue;
 695                  }
 696  
 697                  $id = $extensions[$i]['extnId'];
 698                  $value = &$extensions[$i]['extnValue'];
 699  
 700                  switch ($id) {
 701                      case 'id-ce-certificatePolicies':
 702                          for ($j = 0; $j < count($value); $j++) {
 703                              if (!isset($value[$j]['policyQualifiers'])) {
 704                                  continue;
 705                              }
 706                              for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
 707                                  $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
 708                                  $map = $this->getMapping($subid);
 709                                  $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
 710                                  if ($map !== false) {
 711                                      // by default \phpseclib3\File\ASN1 will try to render qualifier as a \phpseclib3\File\ASN1::TYPE_IA5_STRING since it's
 712                                      // actual type is \phpseclib3\File\ASN1::TYPE_ANY
 713                                      $subvalue = new Element(ASN1::encodeDER($subvalue, $map));
 714                                  }
 715                              }
 716                          }
 717                          break;
 718                      case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
 719                          if (isset($value['authorityCertSerialNumber'])) {
 720                              if ($value['authorityCertSerialNumber']->toBytes() == '') {
 721                                  $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
 722                                  $value['authorityCertSerialNumber'] = new Element($temp);
 723                              }
 724                          }
 725                  }
 726  
 727                  /* [extnValue] contains the DER encoding of an ASN.1 value
 728                     corresponding to the extension type identified by extnID */
 729                  $map = $this->getMapping($id);
 730                  if (is_bool($map)) {
 731                      if (!$map) {
 732                          //user_error($id . ' is not a currently supported extension');
 733                          unset($extensions[$i]);
 734                      }
 735                  } else {
 736                      $value = ASN1::encodeDER($value, $map, ['iPAddress' => [static::class, 'encodeIP']]);
 737                  }
 738              }
 739          }
 740      }
 741  
 742      /**
 743       * Map attribute values from ANY type to attribute-specific internal
 744       *   format.
 745       *
 746       * @param array $root (by reference)
 747       * @param string $path
 748       * @access private
 749       */
 750      private function mapInAttributes(&$root, $path)
 751      {
 752          $attributes = &$this->subArray($root, $path);
 753  
 754          if (is_array($attributes)) {
 755              for ($i = 0; $i < count($attributes); $i++) {
 756                  $id = $attributes[$i]['type'];
 757                  /* $value contains the DER encoding of an ASN.1 value
 758                     corresponding to the attribute type identified by type */
 759                  $map = $this->getMapping($id);
 760                  if (is_array($attributes[$i]['value'])) {
 761                      $values = &$attributes[$i]['value'];
 762                      for ($j = 0; $j < count($values); $j++) {
 763                          $value = ASN1::encodeDER($values[$j], Maps\AttributeValue::MAP);
 764                          $decoded = ASN1::decodeBER($value);
 765                          if (!is_bool($map)) {
 766                              $mapped = ASN1::asn1map($decoded[0], $map);
 767                              if ($mapped !== false) {
 768                                  $values[$j] = $mapped;
 769                              }
 770                              if ($id == 'pkcs-9-at-extensionRequest' && $this->isSubArrayValid($values, $j)) {
 771                                  $this->mapInExtensions($values, $j);
 772                              }
 773                          } elseif ($map) {
 774                              $values[$j] = $value;
 775                          }
 776                      }
 777                  }
 778              }
 779          }
 780      }
 781  
 782      /**
 783       * Map attribute values from attribute-specific internal format to
 784       *   ANY type.
 785       *
 786       * @param array $root (by reference)
 787       * @param string $path
 788       * @access private
 789       */
 790      private function mapOutAttributes(&$root, $path)
 791      {
 792          $attributes = &$this->subArray($root, $path);
 793  
 794          if (is_array($attributes)) {
 795              $size = count($attributes);
 796              for ($i = 0; $i < $size; $i++) {
 797                  /* [value] contains the DER encoding of an ASN.1 value
 798                     corresponding to the attribute type identified by type */
 799                  $id = $attributes[$i]['type'];
 800                  $map = $this->getMapping($id);
 801                  if ($map === false) {
 802                      //user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
 803                      unset($attributes[$i]);
 804                  } elseif (is_array($attributes[$i]['value'])) {
 805                      $values = &$attributes[$i]['value'];
 806                      for ($j = 0; $j < count($values); $j++) {
 807                          switch ($id) {
 808                              case 'pkcs-9-at-extensionRequest':
 809                                  $this->mapOutExtensions($values, $j);
 810                                  break;
 811                          }
 812  
 813                          if (!is_bool($map)) {
 814                              $temp = ASN1::encodeDER($values[$j], $map);
 815                              $decoded = ASN1::decodeBER($temp);
 816                              $values[$j] = ASN1::asn1map($decoded[0], Maps\AttributeValue::MAP);
 817                          }
 818                      }
 819                  }
 820              }
 821          }
 822      }
 823  
 824      /**
 825       * Map DN values from ANY type to DN-specific internal
 826       *   format.
 827       *
 828       * @param array $root (by reference)
 829       * @param string $path
 830       * @access private
 831       */
 832      private function mapInDNs(&$root, $path)
 833      {
 834          $dns = &$this->subArray($root, $path);
 835  
 836          if (is_array($dns)) {
 837              for ($i = 0; $i < count($dns); $i++) {
 838                  for ($j = 0; $j < count($dns[$i]); $j++) {
 839                      $type = $dns[$i][$j]['type'];
 840                      $value = &$dns[$i][$j]['value'];
 841                      if (is_object($value) && $value instanceof Element) {
 842                          $map = $this->getMapping($type);
 843                          if (!is_bool($map)) {
 844                              $decoded = ASN1::decodeBER($value);
 845                              $value = ASN1::asn1map($decoded[0], $map);
 846                          }
 847                      }
 848                  }
 849              }
 850          }
 851      }
 852  
 853      /**
 854       * Map DN values from DN-specific internal format to
 855       *   ANY type.
 856       *
 857       * @param array $root (by reference)
 858       * @param string $path
 859       * @access private
 860       */
 861      private function mapOutDNs(&$root, $path)
 862      {
 863          $dns = &$this->subArray($root, $path);
 864  
 865          if (is_array($dns)) {
 866              $size = count($dns);
 867              for ($i = 0; $i < $size; $i++) {
 868                  for ($j = 0; $j < count($dns[$i]); $j++) {
 869                      $type = $dns[$i][$j]['type'];
 870                      $value = &$dns[$i][$j]['value'];
 871                      if (is_object($value) && $value instanceof Element) {
 872                          continue;
 873                      }
 874  
 875                      $map = $this->getMapping($type);
 876                      if (!is_bool($map)) {
 877                          $value = new Element(ASN1::encodeDER($value, $map));
 878                      }
 879                  }
 880              }
 881          }
 882      }
 883  
 884      /**
 885       * Associate an extension ID to an extension mapping
 886       *
 887       * @param string $extnId
 888       * @access private
 889       * @return mixed
 890       */
 891      private function getMapping($extnId)
 892      {
 893          if (!is_string($extnId)) { // eg. if it's a \phpseclib3\File\ASN1\Element object
 894              return true;
 895          }
 896  
 897          if (isset(self::$extensions[$extnId])) {
 898              return self::$extensions[$extnId];
 899          }
 900  
 901          switch ($extnId) {
 902              case 'id-ce-keyUsage':
 903                  return Maps\KeyUsage::MAP;
 904              case 'id-ce-basicConstraints':
 905                  return Maps\BasicConstraints::MAP;
 906              case 'id-ce-subjectKeyIdentifier':
 907                  return Maps\KeyIdentifier::MAP;
 908              case 'id-ce-cRLDistributionPoints':
 909                  return Maps\CRLDistributionPoints::MAP;
 910              case 'id-ce-authorityKeyIdentifier':
 911                  return Maps\AuthorityKeyIdentifier::MAP;
 912              case 'id-ce-certificatePolicies':
 913                  return Maps\CertificatePolicies::MAP;
 914              case 'id-ce-extKeyUsage':
 915                  return Maps\ExtKeyUsageSyntax::MAP;
 916              case 'id-pe-authorityInfoAccess':
 917                  return Maps\AuthorityInfoAccessSyntax::MAP;
 918              case 'id-ce-subjectAltName':
 919                  return Maps\SubjectAltName::MAP;
 920              case 'id-ce-subjectDirectoryAttributes':
 921                  return Maps\SubjectDirectoryAttributes::MAP;
 922              case 'id-ce-privateKeyUsagePeriod':
 923                  return Maps\PrivateKeyUsagePeriod::MAP;
 924              case 'id-ce-issuerAltName':
 925                  return Maps\IssuerAltName::MAP;
 926              case 'id-ce-policyMappings':
 927                  return Maps\PolicyMappings::MAP;
 928              case 'id-ce-nameConstraints':
 929                  return Maps\NameConstraints::MAP;
 930  
 931              case 'netscape-cert-type':
 932                  return Maps\netscape_cert_type::MAP;
 933              case 'netscape-comment':
 934                  return Maps\netscape_comment::MAP;
 935              case 'netscape-ca-policy-url':
 936                  return Maps\netscape_ca_policy_url::MAP;
 937  
 938              // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
 939              // back around to asn1map() and we don't want it decoded again.
 940              //case 'id-qt-cps':
 941              //    return Maps\CPSuri::MAP;
 942              case 'id-qt-unotice':
 943                  return Maps\UserNotice::MAP;
 944  
 945              // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
 946              case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
 947              case 'entrustVersInfo':
 948              // http://support.microsoft.com/kb/287547
 949              case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
 950              case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
 951              // "SET Secure Electronic Transaction Specification"
 952              // http://www.maithean.com/docs/set_bk3.pdf
 953              case '2.23.42.7.0': // id-set-hashedRootKey
 954              // "Certificate Transparency"
 955              // https://tools.ietf.org/html/rfc6962
 956              case '1.3.6.1.4.1.11129.2.4.2':
 957              // "Qualified Certificate statements"
 958              // https://tools.ietf.org/html/rfc3739#section-3.2.6
 959              case '1.3.6.1.5.5.7.1.3':
 960                  return true;
 961  
 962              // CSR attributes
 963              case 'pkcs-9-at-unstructuredName':
 964                  return Maps\PKCS9String::MAP;
 965              case 'pkcs-9-at-challengePassword':
 966                  return Maps\DirectoryString::MAP;
 967              case 'pkcs-9-at-extensionRequest':
 968                  return Maps\Extensions::MAP;
 969  
 970              // CRL extensions.
 971              case 'id-ce-cRLNumber':
 972                  return Maps\CRLNumber::MAP;
 973              case 'id-ce-deltaCRLIndicator':
 974                  return Maps\CRLNumber::MAP;
 975              case 'id-ce-issuingDistributionPoint':
 976                  return Maps\IssuingDistributionPoint::MAP;
 977              case 'id-ce-freshestCRL':
 978                  return Maps\CRLDistributionPoints::MAP;
 979              case 'id-ce-cRLReasons':
 980                  return Maps\CRLReason::MAP;
 981              case 'id-ce-invalidityDate':
 982                  return Maps\InvalidityDate::MAP;
 983              case 'id-ce-certificateIssuer':
 984                  return Maps\CertificateIssuer::MAP;
 985              case 'id-ce-holdInstructionCode':
 986                  return Maps\HoldInstructionCode::MAP;
 987              case 'id-at-postalAddress':
 988                  return Maps\PostalAddress::MAP;
 989          }
 990  
 991          return false;
 992      }
 993  
 994      /**
 995       * Load an X.509 certificate as a certificate authority
 996       *
 997       * @param string $cert
 998       * @access public
 999       * @return bool
1000       */
1001      public function loadCA($cert)
1002      {
1003          $olddn = $this->dn;
1004          $oldcert = $this->currentCert;
1005          $oldsigsubj = $this->signatureSubject;
1006          $oldkeyid = $this->currentKeyIdentifier;
1007  
1008          $cert = $this->loadX509($cert);
1009          if (!$cert) {
1010              $this->dn = $olddn;
1011              $this->currentCert = $oldcert;
1012              $this->signatureSubject = $oldsigsubj;
1013              $this->currentKeyIdentifier = $oldkeyid;
1014  
1015              return false;
1016          }
1017  
1018          /* From RFC5280 "PKIX Certificate and CRL Profile":
1019  
1020             If the keyUsage extension is present, then the subject public key
1021             MUST NOT be used to verify signatures on certificates or CRLs unless
1022             the corresponding keyCertSign or cRLSign bit is set. */
1023          //$keyUsage = $this->getExtension('id-ce-keyUsage');
1024          //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
1025          //    return false;
1026          //}
1027  
1028          /* From RFC5280 "PKIX Certificate and CRL Profile":
1029  
1030             The cA boolean indicates whether the certified public key may be used
1031             to verify certificate signatures.  If the cA boolean is not asserted,
1032             then the keyCertSign bit in the key usage extension MUST NOT be
1033             asserted.  If the basic constraints extension is not present in a
1034             version 3 certificate, or the extension is present but the cA boolean
1035             is not asserted, then the certified public key MUST NOT be used to
1036             verify certificate signatures. */
1037          //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
1038          //if (!$basicConstraints || !$basicConstraints['cA']) {
1039          //    return false;
1040          //}
1041  
1042          $this->CAs[] = $cert;
1043  
1044          $this->dn = $olddn;
1045          $this->currentCert = $oldcert;
1046          $this->signatureSubject = $oldsigsubj;
1047  
1048          return true;
1049      }
1050  
1051      /**
1052       * Validate an X.509 certificate against a URL
1053       *
1054       * From RFC2818 "HTTP over TLS":
1055       *
1056       * Matching is performed using the matching rules specified by
1057       * [RFC2459].  If more than one identity of a given type is present in
1058       * the certificate (e.g., more than one dNSName name, a match in any one
1059       * of the set is considered acceptable.) Names may contain the wildcard
1060       * character * which is considered to match any single domain name
1061       * component or component fragment. E.g., *.a.com matches foo.a.com but
1062       * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
1063       *
1064       * @param string $url
1065       * @access public
1066       * @return bool
1067       */
1068      public function validateURL($url)
1069      {
1070          if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
1071              return false;
1072          }
1073  
1074          $components = parse_url($url);
1075          if (!isset($components['host'])) {
1076              return false;
1077          }
1078  
1079          if ($names = $this->getExtension('id-ce-subjectAltName')) {
1080              foreach ($names as $name) {
1081                  foreach ($name as $key => $value) {
1082                      $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value);
1083                      switch ($key) {
1084                          case 'dNSName':
1085                              /* From RFC2818 "HTTP over TLS":
1086  
1087                                 If a subjectAltName extension of type dNSName is present, that MUST
1088                                 be used as the identity. Otherwise, the (most specific) Common Name
1089                                 field in the Subject field of the certificate MUST be used. Although
1090                                 the use of the Common Name is existing practice, it is deprecated and
1091                                 Certification Authorities are encouraged to use the dNSName instead. */
1092                              if (preg_match('#^' . $value . '$#', $components['host'])) {
1093                                  return true;
1094                              }
1095                              break;
1096                          case 'iPAddress':
1097                              /* From RFC2818 "HTTP over TLS":
1098  
1099                                 In some cases, the URI is specified as an IP address rather than a
1100                                 hostname. In this case, the iPAddress subjectAltName must be present
1101                                 in the certificate and must exactly match the IP in the URI. */
1102                              if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
1103                                  return true;
1104                              }
1105                      }
1106                  }
1107              }
1108              return false;
1109          }
1110  
1111          if ($value = $this->getDNProp('id-at-commonName')) {
1112              $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value[0]);
1113              return preg_match('#^' . $value . '$#', $components['host']) === 1;
1114          }
1115  
1116          return false;
1117      }
1118  
1119      /**
1120       * Validate a date
1121       *
1122       * If $date isn't defined it is assumed to be the current date.
1123       *
1124       * @param \DateTimeInterface|string $date optional
1125       * @access public
1126       * @return bool
1127       */
1128      public function validateDate($date = null)
1129      {
1130          if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
1131              return false;
1132          }
1133  
1134          if (!isset($date)) {
1135              $date = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
1136          }
1137  
1138          $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
1139          $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];
1140  
1141          $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
1142          $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
1143  
1144          if (is_string($date)) {
1145              $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get()));
1146          }
1147  
1148          $notBefore = new \DateTimeImmutable($notBefore, new \DateTimeZone(@date_default_timezone_get()));
1149          $notAfter = new \DateTimeImmutable($notAfter, new \DateTimeZone(@date_default_timezone_get()));
1150  
1151          return $date >= $notBefore && $date <= $notAfter;
1152      }
1153  
1154      /**
1155       * Fetches a URL
1156       *
1157       * @param string $url
1158       * @access private
1159       * @return bool|string
1160       */
1161      private static function fetchURL($url)
1162      {
1163          if (self::$disable_url_fetch) {
1164              return false;
1165          }
1166  
1167          $parts = parse_url($url);
1168          $data = '';
1169          switch ($parts['scheme']) {
1170              case 'http':
1171                  $fsock = @fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80);
1172                  if (!$fsock) {
1173                      return false;
1174                  }
1175                  fputs($fsock, "GET $parts[path] HTTP/1.0\r\n");
1176                  fputs($fsock, "Host: $parts[host]\r\n\r\n");
1177                  $line = fgets($fsock, 1024);
1178                  if (strlen($line) < 3) {
1179                      return false;
1180                  }
1181                  preg_match('#HTTP/1.\d (\d{3})#', $line, $temp);
1182                  if ($temp[1] != '200') {
1183                      return false;
1184                  }
1185  
1186                  // skip the rest of the headers in the http response
1187                  while (!feof($fsock) && fgets($fsock, 1024) != "\r\n") {
1188                  }
1189  
1190                  while (!feof($fsock)) {
1191                      $temp = fread($fsock, 1024);
1192                      if ($temp === false) {
1193                          return false;
1194                      }
1195                      $data .= $temp;
1196                  }
1197  
1198                  break;
1199              //case 'ftp':
1200              //case 'ldap':
1201              //default:
1202          }
1203  
1204          return $data;
1205      }
1206  
1207      /**
1208       * Validates an intermediate cert as identified via authority info access extension
1209       *
1210       * See https://tools.ietf.org/html/rfc4325 for more info
1211       *
1212       * @param bool $caonly
1213       * @param int $count
1214       * @access private
1215       * @return bool
1216       */
1217      private function testForIntermediate($caonly, $count)
1218      {
1219          $opts = $this->getExtension('id-pe-authorityInfoAccess');
1220          if (!is_array($opts)) {
1221              return false;
1222          }
1223          foreach ($opts as $opt) {
1224              if ($opt['accessMethod'] == 'id-ad-caIssuers') {
1225                  // accessLocation is a GeneralName. GeneralName fields support stuff like email addresses, IP addresses, LDAP,
1226                  // etc, but we're only supporting URI's. URI's and LDAP are the only thing https://tools.ietf.org/html/rfc4325
1227                  // discusses
1228                  if (isset($opt['accessLocation']['uniformResourceIdentifier'])) {
1229                      $url = $opt['accessLocation']['uniformResourceIdentifier'];
1230                      break;
1231                  }
1232              }
1233          }
1234  
1235          if (!isset($url)) {
1236              return false;
1237          }
1238  
1239          $cert = static::fetchURL($url);
1240          if (!is_string($cert)) {
1241              return false;
1242          }
1243  
1244          $parent = new static();
1245          $parent->CAs = $this->CAs;
1246          /*
1247           "Conforming applications that support HTTP or FTP for accessing
1248            certificates MUST be able to accept .cer files and SHOULD be able
1249            to accept .p7c files." -- https://tools.ietf.org/html/rfc4325
1250  
1251           A .p7c file is 'a "certs-only" CMS message as specified in RFC 2797"
1252  
1253           These are currently unsupported
1254          */
1255          if (!is_array($parent->loadX509($cert))) {
1256              return false;
1257          }
1258  
1259          if (!$parent->validateSignatureCountable($caonly, ++$count)) {
1260              return false;
1261          }
1262  
1263          $this->CAs[] = $parent->currentCert;
1264          //$this->loadCA($cert);
1265  
1266          return true;
1267      }
1268  
1269      /**
1270       * Validate a signature
1271       *
1272       * Works on X.509 certs, CSR's and CRL's.
1273       * Returns true if the signature is verified, false if it is not correct or null on error
1274       *
1275       * By default returns false for self-signed certs. Call validateSignature(false) to make this support
1276       * self-signed.
1277       *
1278       * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
1279       *
1280       * @param bool $caonly optional
1281       * @access public
1282       * @return mixed
1283       */
1284      public function validateSignature($caonly = true)
1285      {
1286          return $this->validateSignatureCountable($caonly, 0);
1287      }
1288  
1289      /**
1290       * Validate a signature
1291       *
1292       * Performs said validation whilst keeping track of how many times validation method is called
1293       *
1294       * @param bool $caonly
1295       * @param int $count
1296       * @access private
1297       * @return mixed
1298       */
1299      private function validateSignatureCountable($caonly, $count)
1300      {
1301          if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
1302              return null;
1303          }
1304  
1305          if ($count == self::$recur_limit) {
1306              return false;
1307          }
1308  
1309          /* TODO:
1310             "emailAddress attribute values are not case-sensitive (e.g., "[email protected]" is the same as "[email protected]")."
1311              -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
1312  
1313             implement pathLenConstraint in the id-ce-basicConstraints extension */
1314  
1315          switch (true) {
1316              case isset($this->currentCert['tbsCertificate']):
1317                  // self-signed cert
1318                  switch (true) {
1319                      case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']:
1320                      case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING):
1321                          $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
1322                          $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
1323                          switch (true) {
1324                              case !is_array($authorityKey):
1325                              case !$subjectKeyID:
1326                              case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
1327                                  $signingCert = $this->currentCert; // working cert
1328                          }
1329                  }
1330  
1331                  if (!empty($this->CAs)) {
1332                      for ($i = 0; $i < count($this->CAs); $i++) {
1333                          // even if the cert is a self-signed one we still want to see if it's a CA;
1334                          // if not, we'll conditionally return an error
1335                          $ca = $this->CAs[$i];
1336                          switch (true) {
1337                              case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']:
1338                              case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
1339                                  $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
1340                                  $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
1341                                  switch (true) {
1342                                      case !is_array($authorityKey):
1343                                      case !$subjectKeyID:
1344                                      case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
1345                                          if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) {
1346                                              break 2; // serial mismatch - check other ca
1347                                          }
1348                                          $signingCert = $ca; // working cert
1349                                          break 3;
1350                                  }
1351                          }
1352                      }
1353                      if (count($this->CAs) == $i && $caonly) {
1354                          return $this->testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
1355                      }
1356                  } elseif (!isset($signingCert) || $caonly) {
1357                      return $this->testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
1358                  }
1359                  return $this->validateSignatureHelper(
1360                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
1361                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
1362                      $this->currentCert['signatureAlgorithm']['algorithm'],
1363                      substr($this->currentCert['signature'], 1),
1364                      $this->signatureSubject
1365                  );
1366              case isset($this->currentCert['certificationRequestInfo']):
1367                  return $this->validateSignatureHelper(
1368                      $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
1369                      $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
1370                      $this->currentCert['signatureAlgorithm']['algorithm'],
1371                      substr($this->currentCert['signature'], 1),
1372                      $this->signatureSubject
1373                  );
1374              case isset($this->currentCert['publicKeyAndChallenge']):
1375                  return $this->validateSignatureHelper(
1376                      $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
1377                      $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
1378                      $this->currentCert['signatureAlgorithm']['algorithm'],
1379                      substr($this->currentCert['signature'], 1),
1380                      $this->signatureSubject
1381                  );
1382              case isset($this->currentCert['tbsCertList']):
1383                  if (!empty($this->CAs)) {
1384                      for ($i = 0; $i < count($this->CAs); $i++) {
1385                          $ca = $this->CAs[$i];
1386                          switch (true) {
1387                              case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']:
1388                              case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
1389                                  $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
1390                                  $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
1391                                  switch (true) {
1392                                      case !is_array($authorityKey):
1393                                      case !$subjectKeyID:
1394                                      case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
1395                                          if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) {
1396                                              break 2; // serial mismatch - check other ca
1397                                          }
1398                                          $signingCert = $ca; // working cert
1399                                          break 3;
1400                                  }
1401                          }
1402                      }
1403                  }
1404                  if (!isset($signingCert)) {
1405                      return false;
1406                  }
1407                  return $this->validateSignatureHelper(
1408                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
1409                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
1410                      $this->currentCert['signatureAlgorithm']['algorithm'],
1411                      substr($this->currentCert['signature'], 1),
1412                      $this->signatureSubject
1413                  );
1414              default:
1415                  return false;
1416          }
1417      }
1418  
1419      /**
1420       * Validates a signature
1421       *
1422       * Returns true if the signature is verified and false if it is not correct.
1423       * If the algorithms are unsupposed an exception is thrown.
1424       *
1425       * @param string $publicKeyAlgorithm
1426       * @param string $publicKey
1427       * @param string $signatureAlgorithm
1428       * @param string $signature
1429       * @param string $signatureSubject
1430       * @access private
1431       * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
1432       * @return bool
1433       */
1434      private function validateSignatureHelper($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
1435      {
1436          switch ($publicKeyAlgorithm) {
1437              case 'id-RSASSA-PSS':
1438                  $key = RSA::loadFormat('PSS', $publicKey);
1439                  break;
1440              case 'rsaEncryption':
1441                  $key = RSA::loadFormat('PKCS8', $publicKey);
1442                  switch ($signatureAlgorithm) {
1443                      case 'md2WithRSAEncryption':
1444                      case 'md5WithRSAEncryption':
1445                      case 'sha1WithRSAEncryption':
1446                      case 'sha224WithRSAEncryption':
1447                      case 'sha256WithRSAEncryption':
1448                      case 'sha384WithRSAEncryption':
1449                      case 'sha512WithRSAEncryption':
1450                          $key = $key
1451                              ->withHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm))
1452                              ->withPadding(RSA::SIGNATURE_PKCS1);
1453                          break;
1454                      default:
1455                          throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
1456                  }
1457                  break;
1458              case 'id-Ed25519':
1459              case 'id-Ed448':
1460                  $key = EC::loadFormat('PKCS8', $publicKey);
1461                  break;
1462              case 'id-ecPublicKey':
1463                  $key = EC::loadFormat('PKCS8', $publicKey);
1464                  switch ($signatureAlgorithm) {
1465                      case 'ecdsa-with-SHA1':
1466                      case 'ecdsa-with-SHA224':
1467                      case 'ecdsa-with-SHA256':
1468                      case 'ecdsa-with-SHA384':
1469                      case 'ecdsa-with-SHA512':
1470                          $key = $key
1471                              ->withHash(preg_replace('#^ecdsa-with-#', '', strtolower($signatureAlgorithm)));
1472                          break;
1473                      default:
1474                          throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
1475                  }
1476                  break;
1477              case 'id-dsa':
1478                  $key = DSA::loadFormat('PKCS8', $publicKey);
1479                  switch ($signatureAlgorithm) {
1480                      case 'id-dsa-with-sha1':
1481                      case 'id-dsa-with-sha224':
1482                      case 'id-dsa-with-sha256':
1483                          $key = $key
1484                              ->withHash(preg_replace('#^id-dsa-with-#', '', strtolower($signatureAlgorithm)));
1485                          break;
1486                      default:
1487                          throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
1488                  }
1489                  break;
1490              default:
1491                  throw new UnsupportedAlgorithmException('Public key algorithm unsupported');
1492          }
1493  
1494          return $key->verify($signatureSubject, $signature);
1495      }
1496  
1497      /**
1498       * Sets the recursion limit
1499       *
1500       * When validating a signature it may be necessary to download intermediate certs from URI's.
1501       * An intermediate cert that linked to itself would result in an infinite loop so to prevent
1502       * that we set a recursion limit. A negative number means that there is no recursion limit.
1503       *
1504       * @param int $count
1505       * @access public
1506       */
1507      public static function setRecurLimit($count)
1508      {
1509          self::$recur_limit = $count;
1510      }
1511  
1512      /**
1513       * Prevents URIs from being automatically retrieved
1514       *
1515       * @access public
1516       */
1517      public static function disableURLFetch()
1518      {
1519          self::$disable_url_fetch = true;
1520      }
1521  
1522      /**
1523       * Allows URIs to be automatically retrieved
1524       *
1525       * @access public
1526       */
1527      public static function enableURLFetch()
1528      {
1529          self::$disable_url_fetch = false;
1530      }
1531  
1532      /**
1533       * Decodes an IP address
1534       *
1535       * Takes in a base64 encoded "blob" and returns a human readable IP address
1536       *
1537       * @param string $ip
1538       * @access private
1539       * @return string
1540       */
1541      public static function decodeIP($ip)
1542      {
1543          return inet_ntop($ip);
1544      }
1545  
1546      /**
1547       * Decodes an IP address in a name constraints extension
1548       *
1549       * Takes in a base64 encoded "blob" and returns a human readable IP address / mask
1550       *
1551       * @param string $ip
1552       * @access private
1553       * @return array
1554       */
1555      public static function decodeNameConstraintIP($ip)
1556      {
1557          $size = strlen($ip) >> 1;
1558          $mask = substr($ip, $size);
1559          $ip = substr($ip, 0, $size);
1560          return [inet_ntop($ip), inet_ntop($mask)];
1561      }
1562  
1563      /**
1564       * Encodes an IP address
1565       *
1566       * Takes a human readable IP address into a base64-encoded "blob"
1567       *
1568       * @param string|array $ip
1569       * @access private
1570       * @return string
1571       */
1572      public static function encodeIP($ip)
1573      {
1574          return is_string($ip) ?
1575              inet_pton($ip) :
1576              inet_pton($ip[0]) . inet_pton($ip[1]);
1577      }
1578  
1579      /**
1580       * "Normalizes" a Distinguished Name property
1581       *
1582       * @param string $propName
1583       * @access private
1584       * @return mixed
1585       */
1586      private function translateDNProp($propName)
1587      {
1588          switch (strtolower($propName)) {
1589              case 'id-at-countryname':
1590              case 'countryname':
1591              case 'c':
1592                  return 'id-at-countryName';
1593              case 'id-at-organizationname':
1594              case 'organizationname':
1595              case 'o':
1596                  return 'id-at-organizationName';
1597              case 'id-at-dnqualifier':
1598              case 'dnqualifier':
1599                  return 'id-at-dnQualifier';
1600              case 'id-at-commonname':
1601              case 'commonname':
1602              case 'cn':
1603                  return 'id-at-commonName';
1604              case 'id-at-stateorprovincename':
1605              case 'stateorprovincename':
1606              case 'state':
1607              case 'province':
1608              case 'provincename':
1609              case 'st':
1610                  return 'id-at-stateOrProvinceName';
1611              case 'id-at-localityname':
1612              case 'localityname':
1613              case 'l':
1614                  return 'id-at-localityName';
1615              case 'id-emailaddress':
1616              case 'emailaddress':
1617                  return 'pkcs-9-at-emailAddress';
1618              case 'id-at-serialnumber':
1619              case 'serialnumber':
1620                  return 'id-at-serialNumber';
1621              case 'id-at-postalcode':
1622              case 'postalcode':
1623                  return 'id-at-postalCode';
1624              case 'id-at-streetaddress':
1625              case 'streetaddress':
1626                  return 'id-at-streetAddress';
1627              case 'id-at-name':
1628              case 'name':
1629                  return 'id-at-name';
1630              case 'id-at-givenname':
1631              case 'givenname':
1632                  return 'id-at-givenName';
1633              case 'id-at-surname':
1634              case 'surname':
1635              case 'sn':
1636                  return 'id-at-surname';
1637              case 'id-at-initials':
1638              case 'initials':
1639                  return 'id-at-initials';
1640              case 'id-at-generationqualifier':
1641              case 'generationqualifier':
1642                  return 'id-at-generationQualifier';
1643              case 'id-at-organizationalunitname':
1644              case 'organizationalunitname':
1645              case 'ou':
1646                  return 'id-at-organizationalUnitName';
1647              case 'id-at-pseudonym':
1648              case 'pseudonym':
1649                  return 'id-at-pseudonym';
1650              case 'id-at-title':
1651              case 'title':
1652                  return 'id-at-title';
1653              case 'id-at-description':
1654              case 'description':
1655                  return 'id-at-description';
1656              case 'id-at-role':
1657              case 'role':
1658                  return 'id-at-role';
1659              case 'id-at-uniqueidentifier':
1660              case 'uniqueidentifier':
1661              case 'x500uniqueidentifier':
1662                  return 'id-at-uniqueIdentifier';
1663              case 'postaladdress':
1664              case 'id-at-postaladdress':
1665                  return 'id-at-postalAddress';
1666              default:
1667                  return false;
1668          }
1669      }
1670  
1671      /**
1672       * Set a Distinguished Name property
1673       *
1674       * @param string $propName
1675       * @param mixed $propValue
1676       * @param string $type optional
1677       * @access public
1678       * @return bool
1679       */
1680      public function setDNProp($propName, $propValue, $type = 'utf8String')
1681      {
1682          if (empty($this->dn)) {
1683              $this->dn = ['rdnSequence' => []];
1684          }
1685  
1686          if (($propName = $this->translateDNProp($propName)) === false) {
1687              return false;
1688          }
1689  
1690          foreach ((array) $propValue as $v) {
1691              if (!is_array($v) && isset($type)) {
1692                  $v = [$type => $v];
1693              }
1694              $this->dn['rdnSequence'][] = [
1695                  [
1696                      'type' => $propName,
1697                      'value' => $v
1698                  ]
1699              ];
1700          }
1701  
1702          return true;
1703      }
1704  
1705      /**
1706       * Remove Distinguished Name properties
1707       *
1708       * @param string $propName
1709       * @access public
1710       */
1711      public function removeDNProp($propName)
1712      {
1713          if (empty($this->dn)) {
1714              return;
1715          }
1716  
1717          if (($propName = $this->translateDNProp($propName)) === false) {
1718              return;
1719          }
1720  
1721          $dn = &$this->dn['rdnSequence'];
1722          $size = count($dn);
1723          for ($i = 0; $i < $size; $i++) {
1724              if ($dn[$i][0]['type'] == $propName) {
1725                  unset($dn[$i]);
1726              }
1727          }
1728  
1729          $dn = array_values($dn);
1730          // fix for https://bugs.php.net/75433 affecting PHP 7.2
1731          if (!isset($dn[0])) {
1732              $dn = array_splice($dn, 0, 0);
1733          }
1734      }
1735  
1736      /**
1737       * Get Distinguished Name properties
1738       *
1739       * @param string $propName
1740       * @param array $dn optional
1741       * @param bool $withType optional
1742       * @return mixed
1743       * @access public
1744       */
1745      public function getDNProp($propName, $dn = null, $withType = false)
1746      {
1747          if (!isset($dn)) {
1748              $dn = $this->dn;
1749          }
1750  
1751          if (empty($dn)) {
1752              return false;
1753          }
1754  
1755          if (($propName = $this->translateDNProp($propName)) === false) {
1756              return false;
1757          }
1758  
1759          $filters = [];
1760          $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
1761          ASN1::setFilters($filters);
1762          $this->mapOutDNs($dn, 'rdnSequence');
1763          $dn = $dn['rdnSequence'];
1764          $result = [];
1765          for ($i = 0; $i < count($dn); $i++) {
1766              if ($dn[$i][0]['type'] == $propName) {
1767                  $v = $dn[$i][0]['value'];
1768                  if (!$withType) {
1769                      if (is_array($v)) {
1770                          foreach ($v as $type => $s) {
1771                              $type = array_search($type, ASN1::ANY_MAP);
1772                              if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
1773                                  $s = ASN1::convert($s, $type);
1774                                  if ($s !== false) {
1775                                      $v = $s;
1776                                      break;
1777                                  }
1778                              }
1779                          }
1780                          if (is_array($v)) {
1781                              $v = array_pop($v); // Always strip data type.
1782                          }
1783                      } elseif (is_object($v) && $v instanceof Element) {
1784                          $map = $this->getMapping($propName);
1785                          if (!is_bool($map)) {
1786                              $decoded = ASN1::decodeBER($v);
1787                              $v = ASN1::asn1map($decoded[0], $map);
1788                          }
1789                      }
1790                  }
1791                  $result[] = $v;
1792              }
1793          }
1794  
1795          return $result;
1796      }
1797  
1798      /**
1799       * Set a Distinguished Name
1800       *
1801       * @param mixed $dn
1802       * @param bool $merge optional
1803       * @param string $type optional
1804       * @access public
1805       * @return bool
1806       */
1807      public function setDN($dn, $merge = false, $type = 'utf8String')
1808      {
1809          if (!$merge) {
1810              $this->dn = null;
1811          }
1812  
1813          if (is_array($dn)) {
1814              if (isset($dn['rdnSequence'])) {
1815                  $this->dn = $dn; // No merge here.
1816                  return true;
1817              }
1818  
1819              // handles stuff generated by openssl_x509_parse()
1820              foreach ($dn as $prop => $value) {
1821                  if (!$this->setDNProp($prop, $value, $type)) {
1822                      return false;
1823                  }
1824              }
1825              return true;
1826          }
1827  
1828          // handles everything else
1829          $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
1830          for ($i = 1; $i < count($results); $i += 2) {
1831              $prop = trim($results[$i], ', =/');
1832              $value = $results[$i + 1];
1833              if (!$this->setDNProp($prop, $value, $type)) {
1834                  return false;
1835              }
1836          }
1837  
1838          return true;
1839      }
1840  
1841      /**
1842       * Get the Distinguished Name for a certificates subject
1843       *
1844       * @param mixed $format optional
1845       * @param array $dn optional
1846       * @access public
1847       * @return array|bool|string
1848       */
1849      public function getDN($format = self::DN_ARRAY, $dn = null)
1850      {
1851          if (!isset($dn)) {
1852              $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
1853          }
1854  
1855          switch ((int) $format) {
1856              case self::DN_ARRAY:
1857                  return $dn;
1858              case self::DN_ASN1:
1859                  $filters = [];
1860                  $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
1861                  ASN1::setFilters($filters);
1862                  $this->mapOutDNs($dn, 'rdnSequence');
1863                  return ASN1::encodeDER($dn, Maps\Name::MAP);
1864              case self::DN_CANON:
1865                  //  No SEQUENCE around RDNs and all string values normalized as
1866                  // trimmed lowercase UTF-8 with all spacing as one blank.
1867                  // constructed RDNs will not be canonicalized
1868                  $filters = [];
1869                  $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
1870                  ASN1::setFilters($filters);
1871                  $result = '';
1872                  $this->mapOutDNs($dn, 'rdnSequence');
1873                  foreach ($dn['rdnSequence'] as $rdn) {
1874                      foreach ($rdn as $i => $attr) {
1875                          $attr = &$rdn[$i];
1876                          if (is_array($attr['value'])) {
1877                              foreach ($attr['value'] as $type => $v) {
1878                                  $type = array_search($type, ASN1::ANY_MAP, true);
1879                                  if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
1880                                      $v = ASN1::convert($v, $type);
1881                                      if ($v !== false) {
1882                                          $v = preg_replace('/\s+/', ' ', $v);
1883                                          $attr['value'] = strtolower(trim($v));
1884                                          break;
1885                                      }
1886                                  }
1887                              }
1888                          }
1889                      }
1890                      $result .= ASN1::encodeDER($rdn, Maps\RelativeDistinguishedName::MAP);
1891                  }
1892                  return $result;
1893              case self::DN_HASH:
1894                  $dn = $this->getDN(self::DN_CANON, $dn);
1895                  $hash = new Hash('sha1');
1896                  $hash = $hash->hash($dn);
1897                  extract(unpack('Vhash', $hash));
1898                  return strtolower(Hex::encode(pack('N', $hash)));
1899          }
1900  
1901          // Default is to return a string.
1902          $start = true;
1903          $output = '';
1904  
1905          $result = [];
1906          $filters = [];
1907          $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
1908          ASN1::setFilters($filters);
1909          $this->mapOutDNs($dn, 'rdnSequence');
1910  
1911          foreach ($dn['rdnSequence'] as $field) {
1912              $prop = $field[0]['type'];
1913              $value = $field[0]['value'];
1914  
1915              $delim = ', ';
1916              switch ($prop) {
1917                  case 'id-at-countryName':
1918                      $desc = 'C';
1919                      break;
1920                  case 'id-at-stateOrProvinceName':
1921                      $desc = 'ST';
1922                      break;
1923                  case 'id-at-organizationName':
1924                      $desc = 'O';
1925                      break;
1926                  case 'id-at-organizationalUnitName':
1927                      $desc = 'OU';
1928                      break;
1929                  case 'id-at-commonName':
1930                      $desc = 'CN';
1931                      break;
1932                  case 'id-at-localityName':
1933                      $desc = 'L';
1934                      break;
1935                  case 'id-at-surname':
1936                      $desc = 'SN';
1937                      break;
1938                  case 'id-at-uniqueIdentifier':
1939                      $delim = '/';
1940                      $desc = 'x500UniqueIdentifier';
1941                      break;
1942                  case 'id-at-postalAddress':
1943                      $delim = '/';
1944                      $desc = 'postalAddress';
1945                      break;
1946                  default:
1947                      $delim = '/';
1948                      $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop);
1949              }
1950  
1951              if (!$start) {
1952                  $output .= $delim;
1953              }
1954              if (is_array($value)) {
1955                  foreach ($value as $type => $v) {
1956                      $type = array_search($type, ASN1::ANY_MAP, true);
1957                      if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
1958                          $v = ASN1::convert($v, $type);
1959                          if ($v !== false) {
1960                              $value = $v;
1961                              break;
1962                          }
1963                      }
1964                  }
1965                  if (is_array($value)) {
1966                      $value = array_pop($value); // Always strip data type.
1967                  }
1968              } elseif (is_object($value) && $value instanceof Element) {
1969                  $callback = function ($x) {
1970                      return '\x' . bin2hex($x[0]);
1971                  };
1972                  $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
1973              }
1974              $output .= $desc . '=' . $value;
1975              $result[$desc] = isset($result[$desc]) ?
1976                  array_merge((array) $result[$desc], [$value]) :
1977                  $value;
1978              $start = false;
1979          }
1980  
1981          return $format == self::DN_OPENSSL ? $result : $output;
1982      }
1983  
1984      /**
1985       * Get the Distinguished Name for a certificate/crl issuer
1986       *
1987       * @param int $format optional
1988       * @access public
1989       * @return mixed
1990       */
1991      public function getIssuerDN($format = self::DN_ARRAY)
1992      {
1993          switch (true) {
1994              case !isset($this->currentCert) || !is_array($this->currentCert):
1995                  break;
1996              case isset($this->currentCert['tbsCertificate']):
1997                  return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
1998              case isset($this->currentCert['tbsCertList']):
1999                  return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
2000          }
2001  
2002          return false;
2003      }
2004  
2005      /**
2006       * Get the Distinguished Name for a certificate/csr subject
2007       * Alias of getDN()
2008       *
2009       * @param int $format optional
2010       * @access public
2011       * @return mixed
2012       */
2013      public function getSubjectDN($format = self::DN_ARRAY)
2014      {
2015          switch (true) {
2016              case !empty($this->dn):
2017                  return $this->getDN($format);
2018              case !isset($this->currentCert) || !is_array($this->currentCert):
2019                  break;
2020              case isset($this->currentCert['tbsCertificate']):
2021                  return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
2022              case isset($this->currentCert['certificationRequestInfo']):
2023                  return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
2024          }
2025  
2026          return false;
2027      }
2028  
2029      /**
2030       * Get an individual Distinguished Name property for a certificate/crl issuer
2031       *
2032       * @param string $propName
2033       * @param bool $withType optional
2034       * @access public
2035       * @return mixed
2036       */
2037      public function getIssuerDNProp($propName, $withType = false)
2038      {
2039          switch (true) {
2040              case !isset($this->currentCert) || !is_array($this->currentCert):
2041                  break;
2042              case isset($this->currentCert['tbsCertificate']):
2043                  return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
2044              case isset($this->currentCert['tbsCertList']):
2045                  return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
2046          }
2047  
2048          return false;
2049      }
2050  
2051      /**
2052       * Get an individual Distinguished Name property for a certificate/csr subject
2053       *
2054       * @param string $propName
2055       * @param bool $withType optional
2056       * @access public
2057       * @return mixed
2058       */
2059      public function getSubjectDNProp($propName, $withType = false)
2060      {
2061          switch (true) {
2062              case !empty($this->dn):
2063                  return $this->getDNProp($propName, null, $withType);
2064              case !isset($this->currentCert) || !is_array($this->currentCert):
2065                  break;
2066              case isset($this->currentCert['tbsCertificate']):
2067                  return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
2068              case isset($this->currentCert['certificationRequestInfo']):
2069                  return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
2070          }
2071  
2072          return false;
2073      }
2074  
2075      /**
2076       * Get the certificate chain for the current cert
2077       *
2078       * @access public
2079       * @return mixed
2080       */
2081      public function getChain()
2082      {
2083          $chain = [$this->currentCert];
2084  
2085          if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2086              return false;
2087          }
2088          if (empty($this->CAs)) {
2089              return $chain;
2090          }
2091          while (true) {
2092              $currentCert = $chain[count($chain) - 1];
2093              for ($i = 0; $i < count($this->CAs); $i++) {
2094                  $ca = $this->CAs[$i];
2095                  if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
2096                      $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
2097                      $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2098                      switch (true) {
2099                          case !is_array($authorityKey):
2100                          case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2101                              if ($currentCert === $ca) {
2102                                  break 3;
2103                              }
2104                              $chain[] = $ca;
2105                              break 2;
2106                      }
2107                  }
2108              }
2109              if ($i == count($this->CAs)) {
2110                  break;
2111              }
2112          }
2113          foreach ($chain as $key => $value) {
2114              $chain[$key] = new X509();
2115              $chain[$key]->loadX509($value);
2116          }
2117          return $chain;
2118      }
2119  
2120      /**
2121       * Returns the current cert
2122       *
2123       * @access public
2124       * @return array|bool
2125       */
2126      public function &getCurrentCert()
2127      {
2128          return $this->currentCert;
2129      }
2130  
2131      /**
2132       * Set public key
2133       *
2134       * Key needs to be a \phpseclib3\Crypt\RSA object
2135       *
2136       * @param PublicKey $key
2137       * @access public
2138       * @return void
2139       */
2140      public function setPublicKey(PublicKey $key)
2141      {
2142          $this->publicKey = $key;
2143      }
2144  
2145      /**
2146       * Set private key
2147       *
2148       * Key needs to be a \phpseclib3\Crypt\RSA object
2149       *
2150       * @param PrivateKey $key
2151       * @access public
2152       */
2153      public function setPrivateKey(PrivateKey $key)
2154      {
2155          $this->privateKey = $key;
2156      }
2157  
2158      /**
2159       * Set challenge
2160       *
2161       * Used for SPKAC CSR's
2162       *
2163       * @param string $challenge
2164       * @access public
2165       */
2166      public function setChallenge($challenge)
2167      {
2168          $this->challenge = $challenge;
2169      }
2170  
2171      /**
2172       * Gets the public key
2173       *
2174       * Returns a \phpseclib3\Crypt\RSA object or a false.
2175       *
2176       * @access public
2177       * @return mixed
2178       */
2179      public function getPublicKey()
2180      {
2181          if (isset($this->publicKey)) {
2182              return $this->publicKey;
2183          }
2184  
2185          if (isset($this->currentCert) && is_array($this->currentCert)) {
2186              $paths = [
2187                  'tbsCertificate/subjectPublicKeyInfo',
2188                  'certificationRequestInfo/subjectPKInfo',
2189                  'publicKeyAndChallenge/spki'
2190              ];
2191              foreach ($paths as $path) {
2192                  $keyinfo = $this->subArray($this->currentCert, $path);
2193                  if (!empty($keyinfo)) {
2194                      break;
2195                  }
2196              }
2197          }
2198          if (empty($keyinfo)) {
2199              return false;
2200          }
2201  
2202          $key = $keyinfo['subjectPublicKey'];
2203  
2204          switch ($keyinfo['algorithm']['algorithm']) {
2205              case 'id-RSASSA-PSS':
2206                  return RSA::loadFormat('PSS', $key);
2207              case 'rsaEncryption':
2208                  return RSA::loadFormat('PKCS8', $key)->withPadding(RSA::SIGNATURE_PKCS1);
2209              case 'id-ecPublicKey':
2210              case 'id-Ed25519':
2211              case 'id-Ed448':
2212                  return EC::loadFormat('PKCS8', $key);
2213              case 'id-dsa':
2214                  return DSA::loadFormat('PKCS8', $key);
2215          }
2216  
2217          return false;
2218      }
2219  
2220      /**
2221       * Load a Certificate Signing Request
2222       *
2223       * @param string $csr
2224       * @param int $mode
2225       * @return mixed
2226       * @access public
2227       */
2228      public function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
2229      {
2230          if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
2231              unset($this->currentCert);
2232              unset($this->currentKeyIdentifier);
2233              unset($this->signatureSubject);
2234              $this->dn = $csr['certificationRequestInfo']['subject'];
2235              if (!isset($this->dn)) {
2236                  return false;
2237              }
2238  
2239              $this->currentCert = $csr;
2240              return $csr;
2241          }
2242  
2243          // see http://tools.ietf.org/html/rfc2986
2244  
2245          if ($mode != self::FORMAT_DER) {
2246              $newcsr = ASN1::extractBER($csr);
2247              if ($mode == self::FORMAT_PEM && $csr == $newcsr) {
2248                  return false;
2249              }
2250              $csr = $newcsr;
2251          }
2252          $orig = $csr;
2253  
2254          if ($csr === false) {
2255              $this->currentCert = false;
2256              return false;
2257          }
2258  
2259          $decoded = ASN1::decodeBER($csr);
2260  
2261          if (empty($decoded)) {
2262              $this->currentCert = false;
2263              return false;
2264          }
2265  
2266          $csr = ASN1::asn1map($decoded[0], Maps\CertificationRequest::MAP);
2267          if (!isset($csr) || $csr === false) {
2268              $this->currentCert = false;
2269              return false;
2270          }
2271  
2272          $this->mapInAttributes($csr, 'certificationRequestInfo/attributes');
2273          $this->mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence');
2274  
2275          $this->dn = $csr['certificationRequestInfo']['subject'];
2276  
2277          $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
2278  
2279          $key = $csr['certificationRequestInfo']['subjectPKInfo'];
2280          $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP);
2281          $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] =
2282              "-----BEGIN PUBLIC KEY-----\r\n" .
2283              chunk_split(base64_encode($key), 64) .
2284              "-----END PUBLIC KEY-----";
2285  
2286          $this->currentKeyIdentifier = null;
2287          $this->currentCert = $csr;
2288  
2289          $this->publicKey = null;
2290          $this->publicKey = $this->getPublicKey();
2291  
2292          return $csr;
2293      }
2294  
2295      /**
2296       * Save CSR request
2297       *
2298       * @param array $csr
2299       * @param int $format optional
2300       * @access public
2301       * @return string
2302       */
2303      public function saveCSR($csr, $format = self::FORMAT_PEM)
2304      {
2305          if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
2306              return false;
2307          }
2308  
2309          switch (true) {
2310              case !($algorithm = $this->subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
2311              case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
2312                  break;
2313              default:
2314                  $csr['certificationRequestInfo']['subjectPKInfo'] = new Element(
2315                      base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))
2316                  );
2317          }
2318  
2319          $filters = [];
2320          $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
2321              = ['type' => ASN1::TYPE_UTF8_STRING];
2322  
2323          ASN1::setFilters($filters);
2324  
2325          $this->mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence');
2326          $this->mapOutAttributes($csr, 'certificationRequestInfo/attributes');
2327          $csr = ASN1::encodeDER($csr, Maps\CertificationRequest::MAP);
2328  
2329          switch ($format) {
2330              case self::FORMAT_DER:
2331                  return $csr;
2332              // case self::FORMAT_PEM:
2333              default:
2334                  return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(Base64::encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
2335          }
2336      }
2337  
2338      /**
2339       * Load a SPKAC CSR
2340       *
2341       * SPKAC's are produced by the HTML5 keygen element:
2342       *
2343       * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
2344       *
2345       * @param string $spkac
2346       * @access public
2347       * @return mixed
2348       */
2349      public function loadSPKAC($spkac)
2350      {
2351          if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
2352              unset($this->currentCert);
2353              unset($this->currentKeyIdentifier);
2354              unset($this->signatureSubject);
2355              $this->currentCert = $spkac;
2356              return $spkac;
2357          }
2358  
2359          // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
2360  
2361          // OpenSSL produces SPKAC's that are preceded by the string SPKAC=
2362          $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
2363          $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false;
2364          if ($temp != false) {
2365              $spkac = $temp;
2366          }
2367          $orig = $spkac;
2368  
2369          if ($spkac === false) {
2370              $this->currentCert = false;
2371              return false;
2372          }
2373  
2374          $decoded = ASN1::decodeBER($spkac);
2375  
2376          if (empty($decoded)) {
2377              $this->currentCert = false;
2378              return false;
2379          }
2380  
2381          $spkac = ASN1::asn1map($decoded[0], Maps\SignedPublicKeyAndChallenge::MAP);
2382  
2383          if (!isset($spkac) || !is_array($spkac)) {
2384              $this->currentCert = false;
2385              return false;
2386          }
2387  
2388          $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
2389  
2390          $key = $spkac['publicKeyAndChallenge']['spki'];
2391          $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP);
2392          $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] =
2393              "-----BEGIN PUBLIC KEY-----\r\n" .
2394              chunk_split(base64_encode($key), 64) .
2395              "-----END PUBLIC KEY-----";
2396  
2397          $this->currentKeyIdentifier = null;
2398          $this->currentCert = $spkac;
2399  
2400          $this->publicKey = null;
2401          $this->publicKey = $this->getPublicKey();
2402  
2403          return $spkac;
2404      }
2405  
2406      /**
2407       * Save a SPKAC CSR request
2408       *
2409       * @param array $spkac
2410       * @param int $format optional
2411       * @access public
2412       * @return string
2413       */
2414      public function saveSPKAC($spkac, $format = self::FORMAT_PEM)
2415      {
2416          if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
2417              return false;
2418          }
2419  
2420          $algorithm = $this->subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
2421          switch (true) {
2422              case !$algorithm:
2423              case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']):
2424                  break;
2425              default:
2426                  $spkac['publicKeyAndChallenge']['spki'] = new Element(
2427                      base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))
2428                  );
2429          }
2430  
2431          $spkac = ASN1::encodeDER($spkac, Maps\SignedPublicKeyAndChallenge::MAP);
2432  
2433          switch ($format) {
2434              case self::FORMAT_DER:
2435                  return $spkac;
2436              // case self::FORMAT_PEM:
2437              default:
2438                  // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much
2439                  // no other SPKAC decoders phpseclib will use that same format
2440                  return 'SPKAC=' . Base64::encode($spkac);
2441          }
2442      }
2443  
2444      /**
2445       * Load a Certificate Revocation List
2446       *
2447       * @param string $crl
2448       * @param int $mode
2449       * @return mixed
2450       * @access public
2451       */
2452      public function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
2453      {
2454          if (is_array($crl) && isset($crl['tbsCertList'])) {
2455              $this->currentCert = $crl;
2456              unset($this->signatureSubject);
2457              return $crl;
2458          }
2459  
2460          if ($mode != self::FORMAT_DER) {
2461              $newcrl = ASN1::extractBER($crl);
2462              if ($mode == self::FORMAT_PEM && $crl == $newcrl) {
2463                  return false;
2464              }
2465              $crl = $newcrl;
2466          }
2467          $orig = $crl;
2468  
2469          if ($crl === false) {
2470              $this->currentCert = false;
2471              return false;
2472          }
2473  
2474          $decoded = ASN1::decodeBER($crl);
2475  
2476          if (empty($decoded)) {
2477              $this->currentCert = false;
2478              return false;
2479          }
2480  
2481          $crl = ASN1::asn1map($decoded[0], Maps\CertificateList::MAP);
2482          if (!isset($crl) || $crl === false) {
2483              $this->currentCert = false;
2484              return false;
2485          }
2486  
2487          $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
2488  
2489          $this->mapInDNs($crl, 'tbsCertList/issuer/rdnSequence');
2490          if ($this->isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
2491              $this->mapInExtensions($crl, 'tbsCertList/crlExtensions');
2492          }
2493          if ($this->isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
2494              $rclist_ref = &$this->subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
2495              if ($rclist_ref) {
2496                  $rclist = $crl['tbsCertList']['revokedCertificates'];
2497                  foreach ($rclist as $i => $extension) {
2498                      if ($this->isSubArrayValid($rclist, "$i/crlEntryExtensions")) {
2499                          $this->mapInExtensions($rclist_ref, "$i/crlEntryExtensions");
2500                      }
2501                  }
2502              }
2503          }
2504  
2505          $this->currentKeyIdentifier = null;
2506          $this->currentCert = $crl;
2507  
2508          return $crl;
2509      }
2510  
2511      /**
2512       * Save Certificate Revocation List.
2513       *
2514       * @param array $crl
2515       * @param int $format optional
2516       * @access public
2517       * @return string
2518       */
2519      public function saveCRL($crl, $format = self::FORMAT_PEM)
2520      {
2521          if (!is_array($crl) || !isset($crl['tbsCertList'])) {
2522              return false;
2523          }
2524  
2525          $filters = [];
2526          $filters['tbsCertList']['issuer']['rdnSequence']['value']
2527              = ['type' => ASN1::TYPE_UTF8_STRING];
2528          $filters['tbsCertList']['signature']['parameters']
2529              = ['type' => ASN1::TYPE_UTF8_STRING];
2530          $filters['signatureAlgorithm']['parameters']
2531              = ['type' => ASN1::TYPE_UTF8_STRING];
2532  
2533          if (empty($crl['tbsCertList']['signature']['parameters'])) {
2534              $filters['tbsCertList']['signature']['parameters']
2535                  = ['type' => ASN1::TYPE_NULL];
2536          }
2537  
2538          if (empty($crl['signatureAlgorithm']['parameters'])) {
2539              $filters['signatureAlgorithm']['parameters']
2540                  = ['type' => ASN1::TYPE_NULL];
2541          }
2542  
2543          ASN1::setFilters($filters);
2544  
2545          $this->mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence');
2546          $this->mapOutExtensions($crl, 'tbsCertList/crlExtensions');
2547          $rclist = &$this->subArray($crl, 'tbsCertList/revokedCertificates');
2548          if (is_array($rclist)) {
2549              foreach ($rclist as $i => $extension) {
2550                  $this->mapOutExtensions($rclist, "$i/crlEntryExtensions");
2551              }
2552          }
2553  
2554          $crl = ASN1::encodeDER($crl, Maps\CertificateList::MAP);
2555  
2556          switch ($format) {
2557              case self::FORMAT_DER:
2558                  return $crl;
2559              // case self::FORMAT_PEM:
2560              default:
2561                  return "-----BEGIN X509 CRL-----\r\n" . chunk_split(Base64::encode($crl), 64) . '-----END X509 CRL-----';
2562          }
2563      }
2564  
2565      /**
2566       * Helper function to build a time field according to RFC 3280 section
2567       *  - 4.1.2.5 Validity
2568       *  - 5.1.2.4 This Update
2569       *  - 5.1.2.5 Next Update
2570       *  - 5.1.2.6 Revoked Certificates
2571       * by choosing utcTime iff year of date given is before 2050 and generalTime else.
2572       *
2573       * @param string $date in format date('D, d M Y H:i:s O')
2574       * @access private
2575       * @return array|Element
2576       */
2577      private function timeField($date)
2578      {
2579          if ($date instanceof Element) {
2580              return $date;
2581          }
2582          $dateObj = new \DateTimeImmutable($date, new \DateTimeZone('GMT'));
2583          $year = $dateObj->format('Y'); // the same way ASN1.php parses this
2584          if ($year < 2050) {
2585              return ['utcTime' => $date];
2586          } else {
2587              return ['generalTime' => $date];
2588          }
2589      }
2590  
2591      /**
2592       * Sign an X.509 certificate
2593       *
2594       * $issuer's private key needs to be loaded.
2595       * $subject can be either an existing X.509 cert (if you want to resign it),
2596       * a CSR or something with the DN and public key explicitly set.
2597       *
2598       * @param \phpseclib3\File\X509 $issuer
2599       * @param \phpseclib3\File\X509 $subject
2600       * @access public
2601       * @return mixed
2602       */
2603      public function sign($issuer, $subject)
2604      {
2605          if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
2606              return false;
2607          }
2608  
2609          if (isset($subject->publicKey) && !($subjectPublicKey = $subject->formatSubjectPublicKey())) {
2610              return false;
2611          }
2612  
2613          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
2614          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
2615          $signatureAlgorithm = self::identifySignatureAlgorithm($issuer->privateKey);
2616          if ($signatureAlgorithm != 'id-RSASSA-PSS') {
2617              $signatureAlgorithm = ['algorithm' => $signatureAlgorithm];
2618          } else {
2619              $r = PSS::load($issuer->privateKey->withPassword()->toString('PSS'));
2620              $signatureAlgorithm = [
2621                  'algorithm' => 'id-RSASSA-PSS',
2622                  'parameters' => PSS::savePSSParams($r)
2623              ];
2624          }
2625  
2626          if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
2627              $this->currentCert = $subject->currentCert;
2628              $this->currentCert['tbsCertificate']['signature'] = $signatureAlgorithm;
2629              $this->currentCert['signatureAlgorithm'] = $signatureAlgorithm;
2630  
2631  
2632              if (!empty($this->startDate)) {
2633                  $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->timeField($this->startDate);
2634              }
2635              if (!empty($this->endDate)) {
2636                  $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->timeField($this->endDate);
2637              }
2638              if (!empty($this->serialNumber)) {
2639                  $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
2640              }
2641              if (!empty($subject->dn)) {
2642                  $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
2643              }
2644              if (!empty($subject->publicKey)) {
2645                  $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
2646              }
2647              $this->removeExtension('id-ce-authorityKeyIdentifier');
2648              if (isset($subject->domains)) {
2649                  $this->removeExtension('id-ce-subjectAltName');
2650              }
2651          } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
2652              return false;
2653          } else {
2654              if (!isset($subject->publicKey)) {
2655                  return false;
2656              }
2657  
2658              $startDate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
2659              $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O');
2660  
2661              $endDate = new \DateTimeImmutable('+1 year', new \DateTimeZone(@date_default_timezone_get()));
2662              $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O');
2663  
2664              /* "The serial number MUST be a positive integer"
2665                 "Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
2666                  -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2
2667  
2668                 for the integer to be positive the leading bit needs to be 0 hence the
2669                 application of a bitmap
2670              */
2671              $serialNumber = !empty($this->serialNumber) ?
2672                  $this->serialNumber :
2673                  new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256);
2674  
2675              $this->currentCert = [
2676                  'tbsCertificate' =>
2677                      [
2678                          'version' => 'v3',
2679                          'serialNumber' => $serialNumber, // $this->setSerialNumber()
2680                          'signature' => $signatureAlgorithm,
2681                          'issuer' => false, // this is going to be overwritten later
2682                          'validity' => [
2683                              'notBefore' => $this->timeField($startDate), // $this->setStartDate()
2684                              'notAfter' => $this->timeField($endDate)   // $this->setEndDate()
2685                          ],
2686                          'subject' => $subject->dn,
2687                          'subjectPublicKeyInfo' => $subjectPublicKey
2688                      ],
2689                      'signatureAlgorithm' => $signatureAlgorithm,
2690                      'signature'          => false // this is going to be overwritten later
2691              ];
2692  
2693              // Copy extensions from CSR.
2694              $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
2695  
2696              if (!empty($csrexts)) {
2697                  $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
2698              }
2699          }
2700  
2701          $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
2702  
2703          if (isset($issuer->currentKeyIdentifier)) {
2704              $this->setExtension('id-ce-authorityKeyIdentifier', [
2705                      //'authorityCertIssuer' => array(
2706                      //    array(
2707                      //        'directoryName' => $issuer->dn
2708                      //    )
2709                      //),
2710                      'keyIdentifier' => $issuer->currentKeyIdentifier
2711                  ]);
2712              //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
2713              //if (isset($issuer->serialNumber)) {
2714              //    $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
2715              //}
2716              //unset($extensions);
2717          }
2718  
2719          if (isset($subject->currentKeyIdentifier)) {
2720              $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
2721          }
2722  
2723          $altName = [];
2724  
2725          if (isset($subject->domains) && count($subject->domains)) {
2726              $altName = array_map(['\phpseclib3\File\X509', 'dnsName'], $subject->domains);
2727          }
2728  
2729          if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
2730              // should an IP address appear as the CN if no domain name is specified? idk
2731              //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
2732              $ipAddresses = [];
2733              foreach ($subject->ipAddresses as $ipAddress) {
2734                  $encoded = $subject->ipAddress($ipAddress);
2735                  if ($encoded !== false) {
2736                      $ipAddresses[] = $encoded;
2737                  }
2738              }
2739              if (count($ipAddresses)) {
2740                  $altName = array_merge($altName, $ipAddresses);
2741              }
2742          }
2743  
2744          if (!empty($altName)) {
2745              $this->setExtension('id-ce-subjectAltName', $altName);
2746          }
2747  
2748          if ($this->caFlag) {
2749              $keyUsage = $this->getExtension('id-ce-keyUsage');
2750              if (!$keyUsage) {
2751                  $keyUsage = [];
2752              }
2753  
2754              $this->setExtension(
2755                  'id-ce-keyUsage',
2756                  array_values(array_unique(array_merge($keyUsage, ['cRLSign', 'keyCertSign'])))
2757              );
2758  
2759              $basicConstraints = $this->getExtension('id-ce-basicConstraints');
2760              if (!$basicConstraints) {
2761                  $basicConstraints = [];
2762              }
2763  
2764              $this->setExtension(
2765                  'id-ce-basicConstraints',
2766                  array_merge(['cA' => true], $basicConstraints),
2767                  true
2768              );
2769  
2770              if (!isset($subject->currentKeyIdentifier)) {
2771                  $this->setExtension('id-ce-subjectKeyIdentifier', $this->computeKeyIdentifier($this->currentCert), false, false);
2772              }
2773          }
2774  
2775          // resync $this->signatureSubject
2776          // save $tbsCertificate in case there are any \phpseclib3\File\ASN1\Element objects in it
2777          $tbsCertificate = $this->currentCert['tbsCertificate'];
2778          $this->loadX509($this->saveX509($this->currentCert));
2779  
2780          $result = $this->currentCert;
2781          $this->currentCert['signature'] = $result['signature'] = "\0" . $issuer->privateKey->sign($this->signatureSubject);
2782          $result['tbsCertificate'] = $tbsCertificate;
2783  
2784          $this->currentCert = $currentCert;
2785          $this->signatureSubject = $signatureSubject;
2786  
2787          return $result;
2788      }
2789  
2790      /**
2791       * Sign a CSR
2792       *
2793       * @access public
2794       * @return mixed
2795       */
2796      public function signCSR()
2797      {
2798          if (!is_object($this->privateKey) || empty($this->dn)) {
2799              return false;
2800          }
2801  
2802          $origPublicKey = $this->publicKey;
2803          $this->publicKey = $this->privateKey->getPublicKey();
2804          $publicKey = $this->formatSubjectPublicKey();
2805          $this->publicKey = $origPublicKey;
2806  
2807          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
2808          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
2809          $signatureAlgorithm = self::identifySignatureAlgorithm($this->privateKey);
2810  
2811          if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
2812              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
2813              if (!empty($this->dn)) {
2814                  $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
2815              }
2816              $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
2817          } else {
2818              $this->currentCert = [
2819                  'certificationRequestInfo' =>
2820                      [
2821                          'version' => 'v1',
2822                          'subject' => $this->dn,
2823                          'subjectPKInfo' => $publicKey
2824                      ],
2825                      'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
2826                      'signature'          => false // this is going to be overwritten later
2827              ];
2828          }
2829  
2830          // resync $this->signatureSubject
2831          // save $certificationRequestInfo in case there are any \phpseclib3\File\ASN1\Element objects in it
2832          $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
2833          $this->loadCSR($this->saveCSR($this->currentCert));
2834  
2835          $result = $this->currentCert;
2836          $this->currentCert['signature'] = $result['signature'] = "\0" . $this->privateKey->sign($this->signatureSubject);
2837          $result['certificationRequestInfo'] = $certificationRequestInfo;
2838  
2839          $this->currentCert = $currentCert;
2840          $this->signatureSubject = $signatureSubject;
2841  
2842          return $result;
2843      }
2844  
2845      /**
2846       * Sign a SPKAC
2847       *
2848       * @access public
2849       * @return mixed
2850       */
2851      public function signSPKAC()
2852      {
2853          if (!is_object($this->privateKey)) {
2854              return false;
2855          }
2856  
2857          $origPublicKey = $this->publicKey;
2858          $this->publicKey = $this->privateKey->getPublicKey();
2859          $publicKey = $this->formatSubjectPublicKey();
2860          $this->publicKey = $origPublicKey;
2861  
2862          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
2863          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
2864          $signatureAlgorithm = self::identifySignatureAlgorithm($this->privateKey);
2865  
2866          // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
2867          if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
2868              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
2869              $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
2870              if (!empty($this->challenge)) {
2871                  // the bitwise AND ensures that the output is a valid IA5String
2872                  $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
2873              }
2874          } else {
2875              $this->currentCert = [
2876                  'publicKeyAndChallenge' =>
2877                      [
2878                          'spki' => $publicKey,
2879                          // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
2880                          // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
2881                          // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
2882                          // we could alternatively do this instead if we ignored the specs:
2883                          // Random::string(8) & str_repeat("\x7F", 8)
2884                          'challenge' => !empty($this->challenge) ? $this->challenge : ''
2885                      ],
2886                      'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
2887                      'signature'          => false // this is going to be overwritten later
2888              ];
2889          }
2890  
2891          // resync $this->signatureSubject
2892          // save $publicKeyAndChallenge in case there are any \phpseclib3\File\ASN1\Element objects in it
2893          $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
2894          $this->loadSPKAC($this->saveSPKAC($this->currentCert));
2895  
2896          $result = $this->currentCert;
2897          $this->currentCert['signature'] = $result['signature'] = "\0" . $this->privateKey->sign($this->signatureSubject);
2898          $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
2899  
2900          $this->currentCert = $currentCert;
2901          $this->signatureSubject = $signatureSubject;
2902  
2903          return $result;
2904      }
2905  
2906      /**
2907       * Sign a CRL
2908       *
2909       * $issuer's private key needs to be loaded.
2910       *
2911       * @param \phpseclib3\File\X509 $issuer
2912       * @param \phpseclib3\File\X509 $crl
2913       * @access public
2914       * @return mixed
2915       */
2916      public function signCRL($issuer, $crl)
2917      {
2918          if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
2919              return false;
2920          }
2921  
2922          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
2923          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
2924          $signatureAlgorithm = self::identifySignatureAlgorithm($issuer->privateKey);
2925  
2926          $thisUpdate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
2927          $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O');
2928  
2929          if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
2930              $this->currentCert = $crl->currentCert;
2931              $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
2932              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
2933          } else {
2934              $this->currentCert = [
2935                  'tbsCertList' =>
2936                      [
2937                          'version' => 'v2',
2938                          'signature' => ['algorithm' => $signatureAlgorithm],
2939                          'issuer' => false, // this is going to be overwritten later
2940                          'thisUpdate' => $this->timeField($thisUpdate) // $this->setStartDate()
2941                      ],
2942                      'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
2943                      'signature'          => false // this is going to be overwritten later
2944              ];
2945          }
2946  
2947          $tbsCertList = &$this->currentCert['tbsCertList'];
2948          $tbsCertList['issuer'] = $issuer->dn;
2949          $tbsCertList['thisUpdate'] = $this->timeField($thisUpdate);
2950  
2951          if (!empty($this->endDate)) {
2952              $tbsCertList['nextUpdate'] = $this->timeField($this->endDate); // $this->setEndDate()
2953          } else {
2954              unset($tbsCertList['nextUpdate']);
2955          }
2956  
2957          if (!empty($this->serialNumber)) {
2958              $crlNumber = $this->serialNumber;
2959          } else {
2960              $crlNumber = $this->getExtension('id-ce-cRLNumber');
2961              // "The CRL number is a non-critical CRL extension that conveys a
2962              //  monotonically increasing sequence number for a given CRL scope and
2963              //  CRL issuer.  This extension allows users to easily determine when a
2964              //  particular CRL supersedes another CRL."
2965              // -- https://tools.ietf.org/html/rfc5280#section-5.2.3
2966              $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null;
2967          }
2968  
2969          $this->removeExtension('id-ce-authorityKeyIdentifier');
2970          $this->removeExtension('id-ce-issuerAltName');
2971  
2972          // Be sure version >= v2 if some extension found.
2973          $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
2974          if (!$version) {
2975              if (!empty($tbsCertList['crlExtensions'])) {
2976                  $version = 1; // v2.
2977              } elseif (!empty($tbsCertList['revokedCertificates'])) {
2978                  foreach ($tbsCertList['revokedCertificates'] as $cert) {
2979                      if (!empty($cert['crlEntryExtensions'])) {
2980                          $version = 1; // v2.
2981                      }
2982                  }
2983              }
2984  
2985              if ($version) {
2986                  $tbsCertList['version'] = $version;
2987              }
2988          }
2989  
2990          // Store additional extensions.
2991          if (!empty($tbsCertList['version'])) { // At least v2.
2992              if (!empty($crlNumber)) {
2993                  $this->setExtension('id-ce-cRLNumber', $crlNumber);
2994              }
2995  
2996              if (isset($issuer->currentKeyIdentifier)) {
2997                  $this->setExtension('id-ce-authorityKeyIdentifier', [
2998                          //'authorityCertIssuer' => array(
2999                          //    ]
3000                          //        'directoryName' => $issuer->dn
3001                          //    ]
3002                          //),
3003                          'keyIdentifier' => $issuer->currentKeyIdentifier
3004                      ]);
3005                  //$extensions = &$tbsCertList['crlExtensions'];
3006                  //if (isset($issuer->serialNumber)) {
3007                  //    $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3008                  //}
3009                  //unset($extensions);
3010              }
3011  
3012              $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);
3013  
3014              if ($issuerAltName !== false) {
3015                  $this->setExtension('id-ce-issuerAltName', $issuerAltName);
3016              }
3017          }
3018  
3019          if (empty($tbsCertList['revokedCertificates'])) {
3020              unset($tbsCertList['revokedCertificates']);
3021          }
3022  
3023          unset($tbsCertList);
3024  
3025          // resync $this->signatureSubject
3026          // save $tbsCertList in case there are any \phpseclib3\File\ASN1\Element objects in it
3027          $tbsCertList = $this->currentCert['tbsCertList'];
3028          $this->loadCRL($this->saveCRL($this->currentCert));
3029  
3030          $result = $this->currentCert;
3031          $this->currentCert['signature'] = $result['signature'] = "\0" . $issuer->privateKey->sign($this->signatureSubject);
3032          $result['tbsCertList'] = $tbsCertList;
3033  
3034          $this->currentCert = $currentCert;
3035          $this->signatureSubject = $signatureSubject;
3036  
3037          return $result;
3038      }
3039  
3040      /**
3041       * Identify signature algorithm from key settings
3042       *
3043       * @param PrivateKey $key
3044       * @access private
3045       * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
3046       * @return string
3047       */
3048      private static function identifySignatureAlgorithm(PrivateKey $key)
3049      {
3050          if ($key instanceof RSA) {
3051              if ($key->getPadding() & RSA::SIGNATURE_PSS) {
3052                  return 'id-RSASSA-PSS';
3053              }
3054              switch ($key->getHash()) {
3055                  case 'md2':
3056                  case 'md5':
3057                  case 'sha1':
3058                  case 'sha224':
3059                  case 'sha256':
3060                  case 'sha384':
3061                  case 'sha512':
3062                      return $key->getHash() . 'WithRSAEncryption';
3063              }
3064              throw new UnsupportedAlgorithmException('The only supported hash algorithms for RSA are: md2, md5, sha1, sha224, sha256, sha384, sha512');
3065          }
3066  
3067          if ($key instanceof DSA) {
3068              switch ($key->getHash()) {
3069                  case 'sha1':
3070                  case 'sha224':
3071                  case 'sha256':
3072                      return 'id-dsa-with-' . $key->getHash();
3073              }
3074              throw new UnsupportedAlgorithmException('The only supported hash algorithms for DSA are: sha1, sha224, sha256');
3075          }
3076  
3077          if ($key instanceof EC) {
3078              switch ($key->getCurve()) {
3079                  case 'Ed25519':
3080                  case 'Ed448':
3081                      return 'id-' . $key->getCurve();
3082              }
3083              switch ($key->getHash()) {
3084                  case 'sha1':
3085                  case 'sha224':
3086                  case 'sha256':
3087                  case 'sha384':
3088                  case 'sha512':
3089                      return 'ecdsa-with-' . strtoupper($key->getHash());
3090              }
3091              throw new UnsupportedAlgorithmException('The only supported hash algorithms for EC are: sha1, sha224, sha256, sha384, sha512');
3092          }
3093  
3094          throw new UnsupportedAlgorithmException('The only supported public key classes are: RSA, DSA, EC');
3095      }
3096  
3097      /**
3098       * Set certificate start date
3099       *
3100       * @param \DateTimeInterface|string $date
3101       * @access public
3102       */
3103      public function setStartDate($date)
3104      {
3105          if (!is_object($date) || !($date instanceof \DateTimeInterface)) {
3106              $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get()));
3107          }
3108  
3109          $this->startDate = $date->format('D, d M Y H:i:s O');
3110      }
3111  
3112      /**
3113       * Set certificate end date
3114       *
3115       * @param \DateTimeInterface|string $date
3116       * @access public
3117       */
3118      public function setEndDate($date)
3119      {
3120          /*
3121            To indicate that a certificate has no well-defined expiration date,
3122            the notAfter SHOULD be assigned the GeneralizedTime value of
3123            99991231235959Z.
3124  
3125            -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
3126          */
3127          if (is_string($date) && strtolower($date) === 'lifetime') {
3128              $temp = '99991231235959Z';
3129              $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . ASN1::encodeLength(strlen($temp)) . $temp;
3130              $this->endDate = new Element($temp);
3131          } else {
3132              if (!is_object($date) || !($date instanceof \DateTimeInterface)) {
3133                  $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get()));
3134              }
3135  
3136              $this->endDate = $date->format('D, d M Y H:i:s O');
3137          }
3138      }
3139  
3140      /**
3141       * Set Serial Number
3142       *
3143       * @param string $serial
3144       * @param int $base optional
3145       * @access public
3146       */
3147      public function setSerialNumber($serial, $base = -256)
3148      {
3149          $this->serialNumber = new BigInteger($serial, $base);
3150      }
3151  
3152      /**
3153       * Turns the certificate into a certificate authority
3154       *
3155       * @access public
3156       */
3157      public function makeCA()
3158      {
3159          $this->caFlag = true;
3160      }
3161  
3162      /**
3163       * Check for validity of subarray
3164       *
3165       * This is intended for use in conjunction with _subArrayUnchecked(),
3166       * implementing the checks included in _subArray() but without copying
3167       * a potentially large array by passing its reference by-value to is_array().
3168       *
3169       * @param array $root
3170       * @param string $path
3171       * @return boolean
3172       * @access private
3173       */
3174      private function isSubArrayValid($root, $path)
3175      {
3176          if (!is_array($root)) {
3177              return false;
3178          }
3179  
3180          foreach (explode('/', $path) as $i) {
3181              if (!is_array($root)) {
3182                  return false;
3183              }
3184  
3185              if (!isset($root[$i])) {
3186                  return true;
3187              }
3188  
3189              $root = $root[$i];
3190          }
3191  
3192          return true;
3193      }
3194  
3195      /**
3196       * Get a reference to a subarray
3197       *
3198       * This variant of _subArray() does no is_array() checking,
3199       * so $root should be checked with _isSubArrayValid() first.
3200       *
3201       * This is here for performance reasons:
3202       * Passing a reference (i.e. $root) by-value (i.e. to is_array())
3203       * creates a copy. If $root is an especially large array, this is expensive.
3204       *
3205       * @param array $root
3206       * @param string $path  absolute path with / as component separator
3207       * @param bool $create optional
3208       * @access private
3209       * @return array|false
3210       */
3211      private function &subArrayUnchecked(&$root, $path, $create = false)
3212      {
3213          $false = false;
3214  
3215          foreach (explode('/', $path) as $i) {
3216              if (!isset($root[$i])) {
3217                  if (!$create) {
3218                      return $false;
3219                  }
3220  
3221                  $root[$i] = [];
3222              }
3223  
3224              $root = &$root[$i];
3225          }
3226  
3227          return $root;
3228      }
3229  
3230      /**
3231       * Get a reference to a subarray
3232       *
3233       * @param array $root
3234       * @param string $path  absolute path with / as component separator
3235       * @param bool $create optional
3236       * @access private
3237       * @return array|false
3238       */
3239      private function &subArray(&$root, $path, $create = false)
3240      {
3241          $false = false;
3242  
3243          if (!is_array($root)) {
3244              return $false;
3245          }
3246  
3247          foreach (explode('/', $path) as $i) {
3248              if (!is_array($root)) {
3249                  return $false;
3250              }
3251  
3252              if (!isset($root[$i])) {
3253                  if (!$create) {
3254                      return $false;
3255                  }
3256  
3257                  $root[$i] = [];
3258              }
3259  
3260              $root = &$root[$i];
3261          }
3262  
3263          return $root;
3264      }
3265  
3266      /**
3267       * Get a reference to an extension subarray
3268       *
3269       * @param array $root
3270       * @param string $path optional absolute path with / as component separator
3271       * @param bool $create optional
3272       * @access private
3273       * @return array|false
3274       */
3275      private function &extensions(&$root, $path = null, $create = false)
3276      {
3277          if (!isset($root)) {
3278              $root = $this->currentCert;
3279          }
3280  
3281          switch (true) {
3282              case !empty($path):
3283              case !is_array($root):
3284                  break;
3285              case isset($root['tbsCertificate']):
3286                  $path = 'tbsCertificate/extensions';
3287                  break;
3288              case isset($root['tbsCertList']):
3289                  $path = 'tbsCertList/crlExtensions';
3290                  break;
3291              case isset($root['certificationRequestInfo']):
3292                  $pth = 'certificationRequestInfo/attributes';
3293                  $attributes = &$this->subArray($root, $pth, $create);
3294  
3295                  if (is_array($attributes)) {
3296                      foreach ($attributes as $key => $value) {
3297                          if ($value['type'] == 'pkcs-9-at-extensionRequest') {
3298                              $path = "$pth/$key/value/0";
3299                              break 2;
3300                          }
3301                      }
3302                      if ($create) {
3303                          $key = count($attributes);
3304                          $attributes[] = ['type' => 'pkcs-9-at-extensionRequest', 'value' => []];
3305                          $path = "$pth/$key/value/0";
3306                      }
3307                  }
3308                  break;
3309          }
3310  
3311          $extensions = &$this->subArray($root, $path, $create);
3312  
3313          if (!is_array($extensions)) {
3314              $false = false;
3315              return $false;
3316          }
3317  
3318          return $extensions;
3319      }
3320  
3321      /**
3322       * Remove an Extension
3323       *
3324       * @param string $id
3325       * @param string $path optional
3326       * @access private
3327       * @return bool
3328       */
3329      private function removeExtensionHelper($id, $path = null)
3330      {
3331          $extensions = &$this->extensions($this->currentCert, $path);
3332  
3333          if (!is_array($extensions)) {
3334              return false;
3335          }
3336  
3337          $result = false;
3338          foreach ($extensions as $key => $value) {
3339              if ($value['extnId'] == $id) {
3340                  unset($extensions[$key]);
3341                  $result = true;
3342              }
3343          }
3344  
3345          $extensions = array_values($extensions);
3346          // fix for https://bugs.php.net/75433 affecting PHP 7.2
3347          if (!isset($extensions[0])) {
3348              $extensions = array_splice($extensions, 0, 0);
3349          }
3350          return $result;
3351      }
3352  
3353      /**
3354       * Get an Extension
3355       *
3356       * Returns the extension if it exists and false if not
3357       *
3358       * @param string $id
3359       * @param array $cert optional
3360       * @param string $path optional
3361       * @access private
3362       * @return mixed
3363       */
3364      private function getExtensionHelper($id, $cert = null, $path = null)
3365      {
3366          $extensions = $this->extensions($cert, $path);
3367  
3368          if (!is_array($extensions)) {
3369              return false;
3370          }
3371  
3372          foreach ($extensions as $key => $value) {
3373              if ($value['extnId'] == $id) {
3374                  return $value['extnValue'];
3375              }
3376          }
3377  
3378          return false;
3379      }
3380  
3381      /**
3382       * Returns a list of all extensions in use
3383       *
3384       * @param array $cert optional
3385       * @param string $path optional
3386       * @access private
3387       * @return array
3388       */
3389      private function getExtensionsHelper($cert = null, $path = null)
3390      {
3391          $exts = $this->extensions($cert, $path);
3392          $extensions = [];
3393  
3394          if (is_array($exts)) {
3395              foreach ($exts as $extension) {
3396                  $extensions[] = $extension['extnId'];
3397              }
3398          }
3399  
3400          return $extensions;
3401      }
3402  
3403      /**
3404       * Set an Extension
3405       *
3406       * @param string $id
3407       * @param mixed $value
3408       * @param bool $critical optional
3409       * @param bool $replace optional
3410       * @param string $path optional
3411       * @access private
3412       * @return bool
3413       */
3414      private function setExtensionHelper($id, $value, $critical = false, $replace = true, $path = null)
3415      {
3416          $extensions = &$this->extensions($this->currentCert, $path, true);
3417  
3418          if (!is_array($extensions)) {
3419              return false;
3420          }
3421  
3422          $newext = ['extnId'  => $id, 'critical' => $critical, 'extnValue' => $value];
3423  
3424          foreach ($extensions as $key => $value) {
3425              if ($value['extnId'] == $id) {
3426                  if (!$replace) {
3427                      return false;
3428                  }
3429  
3430                  $extensions[$key] = $newext;
3431                  return true;
3432              }
3433          }
3434  
3435          $extensions[] = $newext;
3436          return true;
3437      }
3438  
3439      /**
3440       * Remove a certificate, CSR or CRL Extension
3441       *
3442       * @param string $id
3443       * @access public
3444       * @return bool
3445       */
3446      public function removeExtension($id)
3447      {
3448          return $this->removeExtensionHelper($id);
3449      }
3450  
3451      /**
3452       * Get a certificate, CSR or CRL Extension
3453       *
3454       * Returns the extension if it exists and false if not
3455       *
3456       * @param string $id
3457       * @param array $cert optional
3458       * @param string $path
3459       * @access public
3460       * @return mixed
3461       */
3462      public function getExtension($id, $cert = null, $path = null)
3463      {
3464          return $this->getExtensionHelper($id, $cert, $path);
3465      }
3466  
3467      /**
3468       * Returns a list of all extensions in use in certificate, CSR or CRL
3469       *
3470       * @param array $cert optional
3471       * @param string $path optional
3472       * @access public
3473       * @return array
3474       */
3475      public function getExtensions($cert = null, $path = null)
3476      {
3477          return $this->getExtensionsHelper($cert, $path);
3478      }
3479  
3480      /**
3481       * Set a certificate, CSR or CRL Extension
3482       *
3483       * @param string $id
3484       * @param mixed $value
3485       * @param bool $critical optional
3486       * @param bool $replace optional
3487       * @access public
3488       * @return bool
3489       */
3490      public function setExtension($id, $value, $critical = false, $replace = true)
3491      {
3492          return $this->setExtensionHelper($id, $value, $critical, $replace);
3493      }
3494  
3495      /**
3496       * Remove a CSR attribute.
3497       *
3498       * @param string $id
3499       * @param int $disposition optional
3500       * @access public
3501       * @return bool
3502       */
3503      public function removeAttribute($id, $disposition = self::ATTR_ALL)
3504      {
3505          $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes');
3506  
3507          if (!is_array($attributes)) {
3508              return false;
3509          }
3510  
3511          $result = false;
3512          foreach ($attributes as $key => $attribute) {
3513              if ($attribute['type'] == $id) {
3514                  $n = count($attribute['value']);
3515                  switch (true) {
3516                      case $disposition == self::ATTR_APPEND:
3517                      case $disposition == self::ATTR_REPLACE:
3518                          return false;
3519                      case $disposition >= $n:
3520                          $disposition -= $n;
3521                          break;
3522                      case $disposition == self::ATTR_ALL:
3523                      case $n == 1:
3524                          unset($attributes[$key]);
3525                          $result = true;
3526                          break;
3527                      default:
3528                          unset($attributes[$key]['value'][$disposition]);
3529                          $attributes[$key]['value'] = array_values($attributes[$key]['value']);
3530                          $result = true;
3531                          break;
3532                  }
3533                  if ($result && $disposition != self::ATTR_ALL) {
3534                      break;
3535                  }
3536              }
3537          }
3538  
3539          $attributes = array_values($attributes);
3540          return $result;
3541      }
3542  
3543      /**
3544       * Get a CSR attribute
3545       *
3546       * Returns the attribute if it exists and false if not
3547       *
3548       * @param string $id
3549       * @param int $disposition optional
3550       * @param array $csr optional
3551       * @access public
3552       * @return mixed
3553       */
3554      public function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
3555      {
3556          if (empty($csr)) {
3557              $csr = $this->currentCert;
3558          }
3559  
3560          $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes');
3561  
3562          if (!is_array($attributes)) {
3563              return false;
3564          }
3565  
3566          foreach ($attributes as $key => $attribute) {
3567              if ($attribute['type'] == $id) {
3568                  $n = count($attribute['value']);
3569                  switch (true) {
3570                      case $disposition == self::ATTR_APPEND:
3571                      case $disposition == self::ATTR_REPLACE:
3572                          return false;
3573                      case $disposition == self::ATTR_ALL:
3574                          return $attribute['value'];
3575                      case $disposition >= $n:
3576                          $disposition -= $n;
3577                          break;
3578                      default:
3579                          return $attribute['value'][$disposition];
3580                  }
3581              }
3582          }
3583  
3584          return false;
3585      }
3586  
3587      /**
3588       * Returns a list of all CSR attributes in use
3589       *
3590       * @param array $csr optional
3591       * @access public
3592       * @return array
3593       */
3594      public function getAttributes($csr = null)
3595      {
3596          if (empty($csr)) {
3597              $csr = $this->currentCert;
3598          }
3599  
3600          $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes');
3601          $attrs = [];
3602  
3603          if (is_array($attributes)) {
3604              foreach ($attributes as $attribute) {
3605                  $attrs[] = $attribute['type'];
3606              }
3607          }
3608  
3609          return $attrs;
3610      }
3611  
3612      /**
3613       * Set a CSR attribute
3614       *
3615       * @param string $id
3616       * @param mixed $value
3617       * @param int $disposition optional
3618       * @access public
3619       * @return bool
3620       */
3621      public function setAttribute($id, $value, $disposition = self::ATTR_ALL)
3622      {
3623          $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
3624  
3625          if (!is_array($attributes)) {
3626              return false;
3627          }
3628  
3629          switch ($disposition) {
3630              case self::ATTR_REPLACE:
3631                  $disposition = self::ATTR_APPEND;
3632                  // fall-through
3633              case self::ATTR_ALL:
3634                  $this->removeAttribute($id);
3635                  break;
3636          }
3637  
3638          foreach ($attributes as $key => $attribute) {
3639              if ($attribute['type'] == $id) {
3640                  $n = count($attribute['value']);
3641                  switch (true) {
3642                      case $disposition == self::ATTR_APPEND:
3643                          $last = $key;
3644                          break;
3645                      case $disposition >= $n:
3646                          $disposition -= $n;
3647                          break;
3648                      default:
3649                          $attributes[$key]['value'][$disposition] = $value;
3650                          return true;
3651                  }
3652              }
3653          }
3654  
3655          switch (true) {
3656              case $disposition >= 0:
3657                  return false;
3658              case isset($last):
3659                  $attributes[$last]['value'][] = $value;
3660                  break;
3661              default:
3662                  $attributes[] = ['type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value : [$value]];
3663                  break;
3664          }
3665  
3666          return true;
3667      }
3668  
3669      /**
3670       * Sets the subject key identifier
3671       *
3672       * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
3673       *
3674       * @param string $value
3675       * @access public
3676       */
3677      public function setKeyIdentifier($value)
3678      {
3679          if (empty($value)) {
3680              unset($this->currentKeyIdentifier);
3681          } else {
3682              $this->currentKeyIdentifier = $value;
3683          }
3684      }
3685  
3686      /**
3687       * Compute a public key identifier.
3688       *
3689       * Although key identifiers may be set to any unique value, this function
3690       * computes key identifiers from public key according to the two
3691       * recommended methods (4.2.1.2 RFC 3280).
3692       * Highly polymorphic: try to accept all possible forms of key:
3693       * - Key object
3694       * - \phpseclib3\File\X509 object with public or private key defined
3695       * - Certificate or CSR array
3696       * - \phpseclib3\File\ASN1\Element object
3697       * - PEM or DER string
3698       *
3699       * @param mixed $key optional
3700       * @param int $method optional
3701       * @access public
3702       * @return string binary key identifier
3703       */
3704      public function computeKeyIdentifier($key = null, $method = 1)
3705      {
3706          if (is_null($key)) {
3707              $key = $this;
3708          }
3709  
3710          switch (true) {
3711              case is_string($key):
3712                  break;
3713              case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
3714                  return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
3715              case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
3716                  return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
3717              case !is_object($key):
3718                  return false;
3719              case $key instanceof Element:
3720                  // Assume the element is a bitstring-packed key.
3721                  $decoded = ASN1::decodeBER($key->element);
3722                  if (empty($decoded)) {
3723                      return false;
3724                  }
3725                  $raw = ASN1::asn1map($decoded[0], ['type' => ASN1::TYPE_BIT_STRING]);
3726                  if (empty($raw)) {
3727                      return false;
3728                  }
3729                  // If the key is private, compute identifier from its corresponding public key.
3730                  $key = PublicKeyLoader::load($raw);
3731                  if ($key instanceof PrivateKey) {  // If private.
3732                      return $this->computeKeyIdentifier($key, $method);
3733                  }
3734                  $key = $raw; // Is a public key.
3735                  break;
3736              case $key instanceof X509:
3737                  if (isset($key->publicKey)) {
3738                      return $this->computeKeyIdentifier($key->publicKey, $method);
3739                  }
3740                  if (isset($key->privateKey)) {
3741                      return $this->computeKeyIdentifier($key->privateKey, $method);
3742                  }
3743                  if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
3744                      return $this->computeKeyIdentifier($key->currentCert, $method);
3745                  }
3746                  return false;
3747              default: // Should be a key object (i.e.: \phpseclib3\Crypt\RSA).
3748                  $key = $key->getPublicKey();
3749                  break;
3750          }
3751  
3752          // If in PEM format, convert to binary.
3753          $key = ASN1::extractBER($key);
3754  
3755          // Now we have the key string: compute its sha-1 sum.
3756          $hash = new Hash('sha1');
3757          $hash = $hash->hash($key);
3758  
3759          if ($method == 2) {
3760              $hash = substr($hash, -8);
3761              $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
3762          }
3763  
3764          return $hash;
3765      }
3766  
3767      /**
3768       * Format a public key as appropriate
3769       *
3770       * @access private
3771       * @return array|false
3772       */
3773      private function formatSubjectPublicKey()
3774      {
3775          $format = $this->publicKey instanceof RSA && ($this->publicKey->getPadding() & RSA::SIGNATURE_PSS) ?
3776              'PSS' :
3777              'PKCS8';
3778  
3779          $publicKey = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->toString($format)));
3780  
3781          $decoded = ASN1::decodeBER($publicKey);
3782          $mapped = ASN1::asn1map($decoded[0], Maps\SubjectPublicKeyInfo::MAP);
3783          if (!is_array($mapped)) {
3784              return false;
3785          }
3786  
3787          $mapped['subjectPublicKey'] = $this->publicKey->toString($format);
3788  
3789          return $mapped;
3790      }
3791  
3792      /**
3793       * Set the domain name's which the cert is to be valid for
3794       *
3795       * @param mixed ...$domains
3796       * @access public
3797       * @return void
3798       */
3799      public function setDomain(...$domains)
3800      {
3801          $this->domains = $domains;
3802          $this->removeDNProp('id-at-commonName');
3803          $this->setDNProp('id-at-commonName', $this->domains[0]);
3804      }
3805  
3806      /**
3807       * Set the IP Addresses's which the cert is to be valid for
3808       *
3809       * @access public
3810       * @param mixed[] ...$ipAddresses
3811       */
3812      public function setIPAddress(...$ipAddresses)
3813      {
3814          $this->ipAddresses = $ipAddresses;
3815          /*
3816          if (!isset($this->domains)) {
3817              $this->removeDNProp('id-at-commonName');
3818              $this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
3819          }
3820          */
3821      }
3822  
3823      /**
3824       * Helper function to build domain array
3825       *
3826       * @access private
3827       * @param string $domain
3828       * @return array
3829       */
3830      private function dnsName($domain)
3831      {
3832          return ['dNSName' => $domain];
3833      }
3834  
3835      /**
3836       * Helper function to build IP Address array
3837       *
3838       * (IPv6 is not currently supported)
3839       *
3840       * @access private
3841       * @param string $address
3842       * @return array
3843       */
3844      private function iPAddress($address)
3845      {
3846          return ['iPAddress' => $address];
3847      }
3848  
3849      /**
3850       * Get the index of a revoked certificate.
3851       *
3852       * @param array $rclist
3853       * @param string $serial
3854       * @param bool $create optional
3855       * @access private
3856       * @return int|false
3857       */
3858      private function revokedCertificate(&$rclist, $serial, $create = false)
3859      {
3860          $serial = new BigInteger($serial);
3861  
3862          foreach ($rclist as $i => $rc) {
3863              if (!($serial->compare($rc['userCertificate']))) {
3864                  return $i;
3865              }
3866          }
3867  
3868          if (!$create) {
3869              return false;
3870          }
3871  
3872          $i = count($rclist);
3873          $revocationDate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
3874          $rclist[] = ['userCertificate' => $serial,
3875                            'revocationDate'  => $this->timeField($revocationDate->format('D, d M Y H:i:s O'))];
3876          return $i;
3877      }
3878  
3879      /**
3880       * Revoke a certificate.
3881       *
3882       * @param string $serial
3883       * @param string $date optional
3884       * @access public
3885       * @return bool
3886       */
3887      public function revoke($serial, $date = null)
3888      {
3889          if (isset($this->currentCert['tbsCertList'])) {
3890              if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
3891                  if ($this->revokedCertificate($rclist, $serial) === false) { // If not yet revoked
3892                      if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) {
3893                          if (!empty($date)) {
3894                              $rclist[$i]['revocationDate'] = $this->timeField($date);
3895                          }
3896  
3897                          return true;
3898                      }
3899                  }
3900              }
3901          }
3902  
3903          return false;
3904      }
3905  
3906      /**
3907       * Unrevoke a certificate.
3908       *
3909       * @param string $serial
3910       * @access public
3911       * @return bool
3912       */
3913      public function unrevoke($serial)
3914      {
3915          if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
3916              if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
3917                  unset($rclist[$i]);
3918                  $rclist = array_values($rclist);
3919                  return true;
3920              }
3921          }
3922  
3923          return false;
3924      }
3925  
3926      /**
3927       * Get a revoked certificate.
3928       *
3929       * @param string $serial
3930       * @access public
3931       * @return mixed
3932       */
3933      public function getRevoked($serial)
3934      {
3935          if (is_array($rclist = $this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
3936              if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
3937                  return $rclist[$i];
3938              }
3939          }
3940  
3941          return false;
3942      }
3943  
3944      /**
3945       * List revoked certificates
3946       *
3947       * @param array $crl optional
3948       * @access public
3949       * @return array|bool
3950       */
3951      public function listRevoked($crl = null)
3952      {
3953          if (!isset($crl)) {
3954              $crl = $this->currentCert;
3955          }
3956  
3957          if (!isset($crl['tbsCertList'])) {
3958              return false;
3959          }
3960  
3961          $result = [];
3962  
3963          if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
3964              foreach ($rclist as $rc) {
3965                  $result[] = $rc['userCertificate']->toString();
3966              }
3967          }
3968  
3969          return $result;
3970      }
3971  
3972      /**
3973       * Remove a Revoked Certificate Extension
3974       *
3975       * @param string $serial
3976       * @param string $id
3977       * @access public
3978       * @return bool
3979       */
3980      public function removeRevokedCertificateExtension($serial, $id)
3981      {
3982          if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
3983              if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
3984                  return $this->removeExtensionHelper($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
3985              }
3986          }
3987  
3988          return false;
3989      }
3990  
3991      /**
3992       * Get a Revoked Certificate Extension
3993       *
3994       * Returns the extension if it exists and false if not
3995       *
3996       * @param string $serial
3997       * @param string $id
3998       * @param array $crl optional
3999       * @access public
4000       * @return mixed
4001       */
4002      public function getRevokedCertificateExtension($serial, $id, $crl = null)
4003      {
4004          if (!isset($crl)) {
4005              $crl = $this->currentCert;
4006          }
4007  
4008          if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
4009              if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
4010                  return $this->getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4011              }
4012          }
4013  
4014          return false;
4015      }
4016  
4017      /**
4018       * Returns a list of all extensions in use for a given revoked certificate
4019       *
4020       * @param string $serial
4021       * @param array $crl optional
4022       * @access public
4023       * @return array|bool
4024       */
4025      public function getRevokedCertificateExtensions($serial, $crl = null)
4026      {
4027          if (!isset($crl)) {
4028              $crl = $this->currentCert;
4029          }
4030  
4031          if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
4032              if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
4033                  return $this->getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4034              }
4035          }
4036  
4037          return false;
4038      }
4039  
4040      /**
4041       * Set a Revoked Certificate Extension
4042       *
4043       * @param string $serial
4044       * @param string $id
4045       * @param mixed $value
4046       * @param bool $critical optional
4047       * @param bool $replace optional
4048       * @access public
4049       * @return bool
4050       */
4051      public function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
4052      {
4053          if (isset($this->currentCert['tbsCertList'])) {
4054              if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
4055                  if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) {
4056                      return $this->setExtensionHelper($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4057                  }
4058              }
4059          }
4060  
4061          return false;
4062      }
4063  
4064      /**
4065       * Register the mapping for a custom/unsupported extension.
4066       *
4067       * @param string $id
4068       * @param array $mapping
4069       */
4070      public static function registerExtension($id, array $mapping)
4071      {
4072          if (isset(self::$extensions[$id]) && self::$extensions[$id] !== $mapping) {
4073              throw new \RuntimeException(
4074                  'Extension ' . $id . ' has already been defined with a different mapping.'
4075              );
4076          }
4077  
4078          self::$extensions[$id] = $mapping;
4079      }
4080  
4081      /**
4082       * Register the mapping for a custom/unsupported extension.
4083       *
4084       * @param string $id
4085       *
4086       * @return array|null
4087       */
4088      public static function getRegisteredExtension($id)
4089      {
4090          return isset(self::$extensions[$id]) ? self::$extensions[$id] : null;
4091      }
4092  
4093      /**
4094       * Register the mapping for a custom/unsupported extension.
4095       *
4096       * @param string $id
4097       * @param mixed $value
4098       * @param bool $critical
4099       * @param bool $replace
4100       */
4101      public function setExtensionValue($id, $value, $critical = false, $replace = false)
4102      {
4103          $this->extensionValues[$id] = compact('critical', 'replace', 'value');
4104      }
4105  }


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