PluginProbe
ManageWP Worker / 3.9.28
ManageWP Worker v3.9.28
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 / lib / PHPSecLib / File / X509.php

X509.php in ManageWP Worker 3.9.28, at lib/PHPSecLib/File/X509.php

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