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