Subversion Repositories oidplus

Rev

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