Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

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