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