PluginProbe
ManageWP Worker / 4.9.25
ManageWP Worker v4.9.25
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / src / PHPSecLib / File / X509.php

X509.php in ManageWP Worker 4.9.25, at src/PHPSecLib/File/X509.php

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