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