Subversion Repositories oidplus

Rev

Rev 874 | Rev 1308 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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