Subversion Repositories oidplus

Rev

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

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