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