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