2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5 * Pure-PHP X.509 Parser
9 * Encode and decode X.509 certificates.
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}.
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.
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:
27 * The above copyright notice and this permission notice shall be included in
28 * all copies or substantial portions of the Software.
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
40 * @author Jim Wigginton <terrafrost@php.net>
41 * @copyright MMXII Jim Wigginton
42 * @license http://www.opensource.org/licenses/mit-license.html MIT License
44 * @link htp://phpseclib.sourceforge.net
50 if (!class_exists('File_ASN1')) {
51 require_once('File/ASN1.php');
55 * Flag to only accept signatures signed by certificate authorities
58 * @see File_X509::validateSignature()
60 define('FILE_X509_VALIDATE_SIGNATURE_BY_CA', 1);
64 * @see File_X509::getDN()
67 * Return internal array representation
69 define('FILE_X509_DN_ARRAY', 0); // Internal array representation.
73 define('FILE_X509_DN_STRING', 1);
75 * Return ASN.1 name string
77 define('FILE_X509_DN_ASN1', 2);
79 * Return OpenSSL compatible array
81 define('FILE_X509_DN_OPENSSL', 3);
83 * Return canonical ASN.1 RDNs string
85 define('FILE_X509_DN_CANON', 4);
87 * Return name ash for file indexing
89 define('FILE_X509_DN_HASH', 5);
93 * Pure-PHP X.509 Parser
95 * @author Jim Wigginton <terrafrost@php.net>
102 * ASN.1 syntax for X.509 certificates
110 * ASN.1 syntax for various extensions
115 var $ExtKeyUsageSyntax;
116 var $BasicConstraints;
118 var $CRLDistributionPoints;
119 var $AuthorityKeyIdentifier;
120 var $CertificatePolicies;
121 var $AuthorityInfoAccessSyntax;
123 var $PrivateKeyUsagePeriod;
126 var $NameConstraints;
131 var $netscape_cert_type;
132 var $netscape_comment;
133 var $netscape_ca_policy_url;
136 var $RelativeDistinguishedName;
139 var $IssuingDistributionPoint;
141 var $CertificateIssuer;
145 * ASN.1 syntax for Certificate Signing Requests (RFC2986)
150 var $CertificationRequest;
153 * ASN.1 syntax for Certificate Revocation Lists (RFC5280)
158 var $CertificateList;
185 * Object identifiers for X.509 certificates
189 * @link http://en.wikipedia.org/wiki/Object_identifier
194 * The certificate authorities
202 * The currently loaded certificate
210 * The signature subject
212 * There's no guarantee File_X509 is going to reencode an X.509 cert in the same way it was originally
213 * encoded so we take save the portion of the original cert that the signature would have made for.
218 var $signatureSubject;
221 * Certificate Start Date
229 * Certificate End Date
247 * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
248 * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
253 var $currentKeyIdentifier;
264 * Default Constructor.
271 // Explicitly Tagged Module, 1988 Syntax
272 // http://tools.ietf.org/html/rfc5280#appendix-A.1
274 $DirectoryString = array(
275 'type' => FILE_ASN1_TYPE_CHOICE
,
277 'teletexString' => array('type' => FILE_ASN1_TYPE_TELETEX_STRING
),
278 'printableString' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
),
279 'universalString' => array('type' => FILE_ASN1_TYPE_UNIVERSAL_STRING
),
280 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING
),
281 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING
)
285 $AttributeValue = array('type' => FILE_ASN1_TYPE_ANY
);
287 $AttributeType = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
);
289 $AttributeTypeAndValue = array(
290 'type' => FILE_ASN1_TYPE_SEQUENCE
,
292 'type' => $AttributeType,
293 'value'=> $AttributeValue
298 In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
299 but they can be useful at times when either there is no unique attribute in the entry or you
300 want to ensure that the entry's DN contains some useful identifying information.
302 - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
304 $this->RelativeDistinguishedName
= array(
305 'type' => FILE_ASN1_TYPE_SET
,
308 'children' => $AttributeTypeAndValue
311 // http://tools.ietf.org/html/rfc5280#section-4.1.2.4
312 $RDNSequence = array(
313 'type' => FILE_ASN1_TYPE_SEQUENCE
,
314 // RDNSequence does not define a min or a max, which means it doesn't have one
317 'children' => $this->RelativeDistinguishedName
321 'type' => FILE_ASN1_TYPE_CHOICE
,
323 'rdnSequence' => $RDNSequence
327 // http://tools.ietf.org/html/rfc5280#section-4.1.1.2
328 $AlgorithmIdentifier = array(
329 'type' => FILE_ASN1_TYPE_SEQUENCE
,
331 'algorithm' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
),
332 'parameters' => array(
333 'type' => FILE_ASN1_TYPE_ANY
,
340 A certificate using system MUST reject the certificate if it encounters
341 a critical extension it does not recognize; however, a non-critical
342 extension may be ignored if it is not recognized.
344 http://tools.ietf.org/html/rfc5280#section-4.2
347 'type' => FILE_ASN1_TYPE_SEQUENCE
,
349 'extnId' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
),
351 'type' => FILE_ASN1_TYPE_BOOLEAN
,
355 'extnValue' => array('type' => FILE_ASN1_TYPE_OCTET_STRING
)
360 'type' => FILE_ASN1_TYPE_SEQUENCE
,
362 // technically, it's MAX, but we'll assume anything < 0 is MAX
364 // if 'children' isn't an array then 'min' and 'max' must be defined
365 'children' => $Extension
368 $SubjectPublicKeyInfo = array(
369 'type' => FILE_ASN1_TYPE_SEQUENCE
,
371 'algorithm' => $AlgorithmIdentifier,
372 'subjectPublicKey' => array('type' => FILE_ASN1_TYPE_BIT_STRING
)
376 $UniqueIdentifier = array('type' => FILE_ASN1_TYPE_BIT_STRING
);
379 'type' => FILE_ASN1_TYPE_CHOICE
,
381 'utcTime' => array('type' => FILE_ASN1_TYPE_UTC_TIME
),
382 'generalTime' => array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME
)
386 // http://tools.ietf.org/html/rfc5280#section-4.1.2.5
388 'type' => FILE_ASN1_TYPE_SEQUENCE
,
390 'notBefore' => $Time,
395 $CertificateSerialNumber = array('type' => FILE_ASN1_TYPE_INTEGER
);
398 'type' => FILE_ASN1_TYPE_INTEGER
,
399 'mapping' => array('v1', 'v2', 'v3')
402 // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
403 $TBSCertificate = array(
404 'type' => FILE_ASN1_TYPE_SEQUENCE
,
406 // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
407 // reenforce that fact
414 'serialNumber' => $CertificateSerialNumber,
415 'signature' => $AlgorithmIdentifier,
416 'issuer' => $this->Name
,
417 'validity' => $Validity,
418 'subject' => $this->Name
,
419 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo,
420 // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
421 'issuerUniqueID' => array(
425 ) +
$UniqueIdentifier,
426 'subjectUniqueID' => array(
430 ) +
$UniqueIdentifier,
431 // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
432 // it's not IMPLICIT, it's EXPLICIT
433 'extensions' => array(
441 $this->Certificate
= array(
442 'type' => FILE_ASN1_TYPE_SEQUENCE
,
444 'tbsCertificate' => $TBSCertificate,
445 'signatureAlgorithm' => $AlgorithmIdentifier,
446 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING
)
450 $this->KeyUsage
= array(
451 'type' => FILE_ASN1_TYPE_BIT_STRING
,
465 $this->BasicConstraints
= array(
466 'type' => FILE_ASN1_TYPE_SEQUENCE
,
469 'type' => FILE_ASN1_TYPE_BOOLEAN
,
473 'pathLenConstraint' => array(
474 'type' => FILE_ASN1_TYPE_INTEGER
,
480 $this->KeyIdentifier
= array('type' => FILE_ASN1_TYPE_OCTET_STRING
);
482 $OrganizationalUnitNames = array(
483 'type' => FILE_ASN1_TYPE_SEQUENCE
,
485 'max' => 4, // ub-organizational-units
486 'children' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
)
489 $PersonalName = array(
490 'type' => FILE_ASN1_TYPE_SET
,
493 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING
,
498 'given-name' => array(
499 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING
,
505 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING
,
510 'generation-qualifier' => array(
511 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING
,
519 $NumericUserIdentifier = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING
);
521 $OrganizationName = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
);
523 $PrivateDomainName = array(
524 'type' => FILE_ASN1_TYPE_CHOICE
,
526 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING
),
527 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
)
531 $TerminalIdentifier = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
);
533 $NetworkAddress = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING
);
535 $AdministrationDomainName = array(
536 'type' => FILE_ASN1_TYPE_CHOICE
,
537 // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or
538 // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC
539 'class' => FILE_ASN1_CLASS_APPLICATION
,
542 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING
),
543 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
)
547 $CountryName = array(
548 'type' => FILE_ASN1_TYPE_CHOICE
,
549 // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or
550 // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC
551 'class' => FILE_ASN1_CLASS_APPLICATION
,
554 'x121-dcc-code' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING
),
555 'iso-3166-alpha2-code' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
)
559 $AnotherName = array(
560 'type' => FILE_ASN1_TYPE_SEQUENCE
,
562 'type-id' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
),
564 'type' => FILE_ASN1_TYPE_ANY
,
572 $ExtensionAttribute = array(
573 'type' => FILE_ASN1_TYPE_SEQUENCE
,
575 'extension-attribute-type' => array(
576 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING
,
581 'extension-attribute-value' => array(
582 'type' => FILE_ASN1_TYPE_ANY
,
590 $ExtensionAttributes = array(
591 'type' => FILE_ASN1_TYPE_SET
,
593 'max' => 256, // ub-extension-attributes
594 'children' => $ExtensionAttribute
597 $BuiltInDomainDefinedAttribute = array(
598 'type' => FILE_ASN1_TYPE_SEQUENCE
,
600 'type' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
),
601 'value' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING
)
605 $BuiltInDomainDefinedAttributes = array(
606 'type' => FILE_ASN1_TYPE_SEQUENCE
,
608 'max' => 4, // ub-domain-defined-attributes
609 'children' => $BuiltInDomainDefinedAttribute
612 $BuiltInStandardAttributes = array(
613 'type' => FILE_ASN1_TYPE_SEQUENCE
,
615 'country-name' => array('optional' => true) +
$CountryName,
616 'administration-domain-name' => array('optional' => true) +
$AdministrationDomainName,
617 'network-address' => array(
622 'terminal-identifier' => array(
626 ) +
$TerminalIdentifier,
627 'private-domain-name' => array(
631 ) +
$PrivateDomainName,
632 'organization-name' => array(
636 ) +
$OrganizationName,
637 'numeric-user-identifier' => array(
641 ) +
$NumericUserIdentifier,
642 'personal-name' => array(
647 'organizational-unit-names' => array(
651 ) +
$OrganizationalUnitNames
656 'type' => FILE_ASN1_TYPE_SEQUENCE
,
658 'built-in-standard-attributes' => $BuiltInStandardAttributes,
659 'built-in-domain-defined-attributes' => array('optional' => true) +
$BuiltInDomainDefinedAttributes,
660 'extension-attributes' => array('optional' => true) +
$ExtensionAttributes
664 $EDIPartyName = array(
665 'type' => FILE_ASN1_TYPE_SEQUENCE
,
667 'nameAssigner' => array(
671 ) +
$DirectoryString,
672 // partyName is technically required but File_ASN1 doesn't currently support non-optional constants and
673 // setting it to optional gets the job done in any event.
674 'partyName' => array(
682 $GeneralName = array(
683 'type' => FILE_ASN1_TYPE_CHOICE
,
685 'otherName' => array(
690 'rfc822Name' => array(
691 'type' => FILE_ASN1_TYPE_IA5_STRING
,
697 'type' => FILE_ASN1_TYPE_IA5_STRING
,
702 'x400Address' => array(
707 'directoryName' => array(
712 'ediPartyName' => array(
717 'uniformResourceIdentifier' => array(
718 'type' => FILE_ASN1_TYPE_IA5_STRING
,
723 'iPAddress' => array(
724 'type' => FILE_ASN1_TYPE_OCTET_STRING
,
729 'registeredID' => array(
730 'type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
,
738 $GeneralNames = array(
739 'type' => FILE_ASN1_TYPE_SEQUENCE
,
742 'children' => $GeneralName
745 $this->IssuerAltName
= $GeneralNames;
747 $ReasonFlags = array(
748 'type' => FILE_ASN1_TYPE_BIT_STRING
,
753 'affiliationChanged',
755 'cessationOfOperation',
757 'privilegeWithdrawn',
762 $DistributionPointName = array(
763 'type' => FILE_ASN1_TYPE_CHOICE
,
770 'nameRelativeToCRLIssuer' => array(
774 ) +
$this->RelativeDistinguishedName
778 $DistributionPoint = array(
779 'type' => FILE_ASN1_TYPE_SEQUENCE
,
781 'distributionPoint' => array(
785 ) +
$DistributionPointName,
791 'cRLIssuer' => array(
799 $this->CRLDistributionPoints
= array(
800 'type' => FILE_ASN1_TYPE_SEQUENCE
,
803 'children' => $DistributionPoint
806 $this->AuthorityKeyIdentifier
= array(
807 'type' => FILE_ASN1_TYPE_SEQUENCE
,
809 'keyIdentifier' => array(
813 ) +
$this->KeyIdentifier
,
814 'authorityCertIssuer' => array(
819 'authorityCertSerialNumber' => array(
823 ) +
$CertificateSerialNumber
827 $PolicyQualifierId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
);
829 $PolicyQualifierInfo = array(
830 'type' => FILE_ASN1_TYPE_SEQUENCE
,
832 'policyQualifierId' => $PolicyQualifierId,
833 'qualifier' => array('type' => FILE_ASN1_TYPE_ANY
)
837 $CertPolicyId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
);
839 $PolicyInformation = array(
840 'type' => FILE_ASN1_TYPE_SEQUENCE
,
842 'policyIdentifier' => $CertPolicyId,
843 'policyQualifiers' => array(
844 'type' => FILE_ASN1_TYPE_SEQUENCE
,
848 'children' => $PolicyQualifierInfo
853 $this->CertificatePolicies
= array(
854 'type' => FILE_ASN1_TYPE_SEQUENCE
,
857 'children' => $PolicyInformation
860 $this->PolicyMappings
= array(
861 'type' => FILE_ASN1_TYPE_SEQUENCE
,
865 'type' => FILE_ASN1_TYPE_SEQUENCE
,
867 'issuerDomainPolicy' => $CertPolicyId,
868 'subjectDomainPolicy' => $CertPolicyId
873 $KeyPurposeId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
);
875 $this->ExtKeyUsageSyntax
= array(
876 'type' => FILE_ASN1_TYPE_SEQUENCE
,
879 'children' => $KeyPurposeId
882 $AccessDescription = array(
883 'type' => FILE_ASN1_TYPE_SEQUENCE
,
885 'accessMethod' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER
),
886 'accessLocation' => $GeneralName
890 $this->AuthorityInfoAccessSyntax
= array(
891 'type' => FILE_ASN1_TYPE_SEQUENCE
,
894 'children' => $AccessDescription
897 $this->SubjectAltName
= $GeneralNames;
899 $this->PrivateKeyUsagePeriod
= array(
900 'type' => FILE_ASN1_TYPE_SEQUENCE
,
902 'notBefore' => array(
906 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME
),
911 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME
)
915 $BaseDistance = array('type' => FILE_ASN1_TYPE_INTEGER
);
917 $GeneralSubtree = array(
918 'type' => FILE_ASN1_TYPE_SEQUENCE
,
920 'base' => $GeneralName,
925 'default' => new Math_BigInteger(0)
935 $GeneralSubtrees = array(
936 'type' => FILE_ASN1_TYPE_SEQUENCE
,
939 'children' => $GeneralSubtree
942 $this->NameConstraints
= array(
943 'type' => FILE_ASN1_TYPE_SEQUENCE
,
945 'permittedSubtrees' => array(
949 ) +
$GeneralSubtrees,
950 'excludedSubtrees' => array(
958 $this->CPSuri
= array('type' => FILE_ASN1_TYPE_IA5_STRING
);
960 $DisplayText = array(
961 'type' => FILE_ASN1_TYPE_CHOICE
,
963 'ia5String' => array('type' => FILE_ASN1_TYPE_IA5_STRING
),
964 'visibleString' => array('type' => FILE_ASN1_TYPE_VISIBLE_STRING
),
965 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING
),
966 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING
)
970 $NoticeReference = array(
971 'type' => FILE_ASN1_TYPE_SEQUENCE
,
973 'organization' => $DisplayText,
974 'noticeNumbers' => array(
975 'type' => FILE_ASN1_TYPE_SEQUENCE
,
978 'children' => array('type' => FILE_ASN1_TYPE_INTEGER
)
983 $this->UserNotice
= array(
984 'type' => FILE_ASN1_TYPE_SEQUENCE
,
986 'noticeRef' => array(
989 ) +
$NoticeReference,
990 'explicitText' => array(
997 // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
998 $this->netscape_cert_type
= array(
999 'type' => FILE_ASN1_TYPE_BIT_STRING
,
1012 $this->netscape_comment
= array('type' => FILE_ASN1_TYPE_IA5_STRING
);
1013 $this->netscape_ca_policy_url
= array('type' => FILE_ASN1_TYPE_IA5_STRING
);
1015 // attribute is used in RFC2986 but we're using the RFC5280 definition
1018 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1019 'children' => array(
1020 'type' => $AttributeType,
1022 'type' => FILE_ASN1_TYPE_SET
,
1025 'children' => $AttributeValue
1030 // adapted from <http://tools.ietf.org/html/rfc2986>
1032 $Attributes = array(
1033 'type' => FILE_ASN1_TYPE_SET
,
1036 'children' => $Attribute
1039 $CertificationRequestInfo = array(
1040 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1041 'children' => array(
1043 'type' => FILE_ASN1_TYPE_INTEGER
,
1044 'mapping' => array('v1')
1046 'subject' => $this->Name
,
1047 'subjectPKInfo' => $SubjectPublicKeyInfo,
1048 'attributes' => array(
1056 $this->CertificationRequest
= array(
1057 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1058 'children' => array(
1059 'certificationRequestInfo' => $CertificationRequestInfo,
1060 'signatureAlgorithm' => $AlgorithmIdentifier,
1061 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING
)
1065 $RevokedCertificate = array(
1066 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1067 'children' => array(
1068 'userCertificate' => $CertificateSerialNumber,
1069 'revocationDate' => $Time,
1070 'crlEntryExtensions' => array(
1076 $TBSCertList = array(
1077 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1078 'children' => array(
1083 'signature' => $AlgorithmIdentifier,
1084 'issuer' => $this->Name
,
1085 'thisUpdate' => $Time,
1086 'nextUpdate' => array(
1089 'revokedCertificates' => array(
1090 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1094 'children' => $RevokedCertificate
1096 'crlExtensions' => array(
1104 $this->CertificateList
= array(
1105 'type' => FILE_ASN1_TYPE_SEQUENCE
,
1106 'children' => array(
1107 'tbsCertList' => $TBSCertList,
1108 'signatureAlgorithm' => $AlgorithmIdentifier,
1109 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING
)
1113 $this->CRLNumber
= array('type' => FILE_ASN1_TYPE_INTEGER
);
1115 $this->CRLReason
= array('type' => FILE_ASN1_TYPE_ENUMERATED
,
1120 'affiliationChanged',
1122 'cessationOfOperation',
1124 // Value 7 is not used.
1125 8 => 'removeFromCRL',
1126 'privilegeWithdrawn',
1131 $this->IssuingDistributionPoint
= array('type' => FILE_ASN1_TYPE_SEQUENCE
,
1132 'children' => array(
1133 'distributionPoint' => array(
1137 ) +
$DistributionPointName,
1138 'onlyContainsUserCerts' => array(
1139 'type' => FILE_ASN1_TYPE_BOOLEAN
,
1145 'onlyContainsCACerts' => array(
1146 'type' => FILE_ASN1_TYPE_BOOLEAN
,
1152 'onlySomeReasons' => array(
1157 'indirectCRL' => array(
1158 'type' => FILE_ASN1_TYPE_BOOLEAN
,
1164 'onlyContainsAttributeCerts' => array(
1165 'type' => FILE_ASN1_TYPE_BOOLEAN
,
1174 $this->InvalidityDate
= array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME
);
1176 $this->CertificateIssuer
= $GeneralNames;
1178 // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
1179 $this->oids
= array(
1180 '1.3.6.1.5.5.7' => 'id-pkix',
1181 '1.3.6.1.5.5.7.1' => 'id-pe',
1182 '1.3.6.1.5.5.7.2' => 'id-qt',
1183 '1.3.6.1.5.5.7.3' => 'id-kp',
1184 '1.3.6.1.5.5.7.48' => 'id-ad',
1185 '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
1186 '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
1187 '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
1188 '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
1189 '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
1190 '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
1192 '2.5.4.41' => 'id-at-name',
1193 '2.5.4.4' => 'id-at-surname',
1194 '2.5.4.42' => 'id-at-givenName',
1195 '2.5.4.43' => 'id-at-initials',
1196 '2.5.4.44' => 'id-at-generationQualifier',
1197 '2.5.4.3' => 'id-at-commonName',
1198 '2.5.4.7' => 'id-at-localityName',
1199 '2.5.4.8' => 'id-at-stateOrProvinceName',
1200 '2.5.4.10' => 'id-at-organizationName',
1201 '2.5.4.11' => 'id-at-organizationalUnitName',
1202 '2.5.4.12' => 'id-at-title',
1203 '2.5.4.13' => 'id-at-description',
1204 '2.5.4.46' => 'id-at-dnQualifier',
1205 '2.5.4.6' => 'id-at-countryName',
1206 '2.5.4.5' => 'id-at-serialNumber',
1207 '2.5.4.65' => 'id-at-pseudonym',
1208 '2.5.4.17' => 'id-at-postalCode',
1209 '2.5.4.9' => 'id-at-streetAddress',
1210 '2.5.4.45' => 'id-at-uniqueIdentifier',
1211 '2.5.4.72' => 'id-at-role',
1213 '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
1214 '1.2.840.113549.1.9' => 'pkcs-9',
1215 '1.2.840.113549.1.9.1' => 'id-emailAddress',
1216 '2.5.29' => 'id-ce',
1217 '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
1218 '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
1219 '2.5.29.15' => 'id-ce-keyUsage',
1220 '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
1221 '2.5.29.32' => 'id-ce-certificatePolicies',
1222 '2.5.29.32.0' => 'anyPolicy',
1224 '2.5.29.33' => 'id-ce-policyMappings',
1225 '2.5.29.17' => 'id-ce-subjectAltName',
1226 '2.5.29.18' => 'id-ce-issuerAltName',
1227 '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
1228 '2.5.29.19' => 'id-ce-basicConstraints',
1229 '2.5.29.30' => 'id-ce-nameConstraints',
1230 '2.5.29.36' => 'id-ce-policyConstraints',
1231 '2.5.29.31' => 'id-ce-cRLDistributionPoints',
1232 '2.5.29.37' => 'id-ce-extKeyUsage',
1233 '2.5.29.37.0' => 'anyExtendedKeyUsage',
1234 '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
1235 '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
1236 '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
1237 '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
1238 '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
1239 '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
1240 '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
1241 '2.5.29.46' => 'id-ce-freshestCRL',
1242 '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
1243 '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
1244 '2.5.29.20' => 'id-ce-cRLNumber',
1245 '2.5.29.28' => 'id-ce-issuingDistributionPoint',
1246 '2.5.29.27' => 'id-ce-deltaCRLIndicator',
1247 '2.5.29.21' => 'id-ce-cRLReasons',
1248 '2.5.29.29' => 'id-ce-certificateIssuer',
1249 '2.5.29.23' => 'id-ce-holdInstructionCode',
1250 '2.2.840.10040.2' => 'holdInstruction',
1251 '2.2.840.10040.2.1' => 'id-holdinstruction-none',
1252 '2.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
1253 '2.2.840.10040.2.3' => 'id-holdinstruction-reject',
1254 '2.5.29.24' => 'id-ce-invalidityDate',
1256 '1.2.840.113549.2.2' => 'md2',
1257 '1.2.840.113549.2.5' => 'md5',
1258 '1.3.14.3.2.26' => 'id-sha1',
1259 '1.2.840.10040.4.1' => 'id-dsa',
1260 '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
1261 '1.2.840.113549.1.1' => 'pkcs-1',
1262 '1.2.840.113549.1.1.1' => 'rsaEncryption',
1263 '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
1264 '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
1265 '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
1266 '1.2.840.10046.2.1' => 'dhpublicnumber',
1267 '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
1268 '1.2.840.10045' => 'ansi-X9-62',
1269 '1.2.840.10045.4' => 'id-ecSigType',
1270 '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
1271 '1.2.840.10045.1' => 'id-fieldType',
1272 '1.2.840.10045.1.1' => 'prime-field',
1273 '1.2.840.10045.1.2' => 'characteristic-two-field',
1274 '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
1275 '1.2.840.10045.1.2.3.1' => 'gnBasis',
1276 '1.2.840.10045.1.2.3.2' => 'tpBasis',
1277 '1.2.840.10045.1.2.3.3' => 'ppBasis',
1278 '1.2.840.10045.2' => 'id-publicKeyType',
1279 '1.2.840.10045.2.1' => 'id-ecPublicKey',
1280 '1.2.840.10045.3' => 'ellipticCurve',
1281 '1.2.840.10045.3.0' => 'c-TwoCurve',
1282 '1.2.840.10045.3.0.1' => 'c2pnb163v1',
1283 '1.2.840.10045.3.0.2' => 'c2pnb163v2',
1284 '1.2.840.10045.3.0.3' => 'c2pnb163v3',
1285 '1.2.840.10045.3.0.4' => 'c2pnb176w1',
1286 '1.2.840.10045.3.0.5' => 'c2pnb191v1',
1287 '1.2.840.10045.3.0.6' => 'c2pnb191v2',
1288 '1.2.840.10045.3.0.7' => 'c2pnb191v3',
1289 '1.2.840.10045.3.0.8' => 'c2pnb191v4',
1290 '1.2.840.10045.3.0.9' => 'c2pnb191v5',
1291 '1.2.840.10045.3.0.10' => 'c2pnb208w1',
1292 '1.2.840.10045.3.0.11' => 'c2pnb239v1',
1293 '1.2.840.10045.3.0.12' => 'c2pnb239v2',
1294 '1.2.840.10045.3.0.13' => 'c2pnb239v3',
1295 '1.2.840.10045.3.0.14' => 'c2pnb239v4',
1296 '1.2.840.10045.3.0.15' => 'c2pnb239v5',
1297 '1.2.840.10045.3.0.16' => 'c2pnb272w1',
1298 '1.2.840.10045.3.0.17' => 'c2pnb304w1',
1299 '1.2.840.10045.3.0.18' => 'c2pnb359v1',
1300 '1.2.840.10045.3.0.19' => 'c2pnb368w1',
1301 '1.2.840.10045.3.0.20' => 'c2pnb431r1',
1302 '1.2.840.10045.3.1' => 'primeCurve',
1303 '1.2.840.10045.3.1.1' => 'prime192v1',
1304 '1.2.840.10045.3.1.2' => 'prime192v2',
1305 '1.2.840.10045.3.1.3' => 'prime192v3',
1306 '1.2.840.10045.3.1.4' => 'prime239v1',
1307 '1.2.840.10045.3.1.5' => 'prime239v2',
1308 '1.2.840.10045.3.1.6' => 'prime239v3',
1309 '1.2.840.10045.3.1.7' => 'prime256v1',
1310 '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
1311 '1.2.840.113549.1.1.9' => 'id-pSpecified',
1312 '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
1313 '1.2.840.113549.1.1.8' => 'id-mgf1',
1314 '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
1315 '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
1316 '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
1317 '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
1318 '2.16.840.1.101.3.4.2.4' => 'id-sha224',
1319 '2.16.840.1.101.3.4.2.1' => 'id-sha256',
1320 '2.16.840.1.101.3.4.2.2' => 'id-sha384',
1321 '2.16.840.1.101.3.4.2.3' => 'id-sha512',
1322 '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
1323 '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
1324 '1.2.643.2.2.20' => 'id-GostR3410-2001',
1325 '1.2.643.2.2.19' => 'id-GostR3410-94',
1326 // Netscape Object Identifiers from "Netscape Certificate Extensions"
1327 '2.16.840.1.113730' => 'netscape',
1328 '2.16.840.1.113730.1' => 'netscape-cert-extension',
1329 '2.16.840.1.113730.1.1' => 'netscape-cert-type',
1330 '2.16.840.1.113730.1.13' => 'netscape-comment',
1331 '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
1332 // the following are X.509 extensions not supported by phpseclib
1333 '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
1334 '1.2.840.113533.7.65.0' => 'entrustVersInfo',
1335 '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
1336 // for Certificate Signing Requests
1337 // see http://tools.ietf.org/html/rfc2985
1338 '1.2.840.113549.1.9.2' => 'unstructuredName', // PKCS #9 unstructured name
1339 '1.2.840.113549.1.9.7' => 'challengePassword' // Challenge password for certificate revocations
1344 * Load X.509 certificate
1346 * Returns an associative array describing the X.509 cert or a false if the cert failed to load
1348 * @param String $cert
1352 function loadX509($cert)
1354 if (is_array($cert) && isset($cert['tbsCertificate'])) {
1355 $this->currentCert
= $cert;
1356 unset($this->signatureSubject
);
1360 $asn1 = new File_ASN1();
1363 X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them above and beyond the ceritificate. ie.
1364 some may have the following preceeding the -----BEGIN CERTIFICATE----- line:
1366 subject=/O=organization/OU=org unit/CN=common name
1367 issuer=/O=organization/CN=common name
1369 $temp = preg_replace('#^(?:[^-].+[\r\n]+)+|-.+-|[\r\n]| #', '', $cert);
1370 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ?
base64_decode($temp) : false;
1371 if ($temp != false) {
1375 if ($cert === false) {
1376 $this->currentCert
= false;
1380 $asn1->loadOIDs($this->oids
);
1381 $decoded = $asn1->decodeBER($cert);
1383 if (!empty($decoded)) {
1384 $x509 = $asn1->asn1map($decoded[0], $this->Certificate
);
1386 if (!isset($x509) ||
$x509 === false) {
1387 $this->currentCert
= false;
1391 $this->signatureSubject
= substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
1393 $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
1395 $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
1396 $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
1398 $this->currentCert
= $x509;
1399 $this->dn
= $x509['tbsCertificate']['subject'];
1401 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
1402 $this->currentKeyIdentifier
= is_string($currentKeyIdentifier) ?
$currentKeyIdentifier : NULL;
1408 * Save X.509 certificate
1410 * @param Array $cert
1414 function saveX509($cert)
1416 if (!is_array($cert) ||
!isset($cert['tbsCertificate'])) {
1420 if (is_array($cert['tbsCertificate']['subjectPublicKeyInfo'])) {
1421 switch ($cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm']) {
1422 case 'rsaEncryption':
1423 $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] =
1424 base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
1428 $asn1 = new File_ASN1();
1430 $asn1->loadOIDs($this->oids
);
1433 $filters['tbsCertificate']['signature']['parameters'] =
1434 $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] =
1435 $filters['tbsCertificate']['issuer']['rdnSequence']['value'] =
1436 $filters['tbsCertificate']['subject']['rdnSequence']['value'] =
1437 $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] =
1438 $filters['signatureAlgorithm']['parameters'] =
1439 $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] =
1440 //$filters['policyQualifiers']['qualifier'] =
1441 $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] =
1442 $filters['directoryName']['rdnSequence']['value'] =
1443 array('type' => FILE_ASN1_TYPE_UTF8_STRING
);
1444 /* in the case of policyQualifiers/qualifier, the type has to be FILE_ASN1_TYPE_IA5_STRING.
1445 FILE_ASN1_TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
1448 $filters['policyQualifiers']['qualifier'] =
1449 array('type' => FILE_ASN1_TYPE_IA5_STRING
);
1451 $asn1->loadFilters($filters);
1453 $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1);
1455 $cert = $asn1->encodeDER($cert, $this->Certificate
);
1457 return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert)) . '-----END CERTIFICATE-----';
1461 * Map extension values from octet string to extension-specific internal
1464 * @param Array ref $root
1465 * @param String $path
1466 * @param Object $asn1
1469 function _mapInExtensions(&$root, $path, $asn1)
1471 $extensions = &$this->_subArray($root, $path);
1473 if (is_array($extensions)) {
1474 for ($i = 0; $i < count($extensions); $i++
) {
1475 $id = $extensions[$i]['extnId'];
1476 $value = &$extensions[$i]['extnValue'];
1477 $value = base64_decode($value);
1478 $decoded = $asn1->decodeBER($value);
1479 /* [extnValue] contains the DER encoding of an ASN.1 value
1480 corresponding to the extension type identified by extnID */
1481 $map = $this->_getMapping($id);
1482 if (!is_bool($map)) {
1483 $mapped = $asn1->asn1map($decoded[0], $map);
1484 $value = $mapped === false ?
$decoded[0] : $mapped;
1486 if ($id == 'id-ce-certificatePolicies') {
1487 for ($j = 0; $j < count($value); $j++
) {
1488 if (!isset($value[$j]['policyQualifiers'])) {
1491 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++
) {
1492 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1493 $map = $this->_getMapping($subid);
1494 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1495 if ($map !== false) {
1496 $decoded = $asn1->decodeBER($subvalue);
1497 $mapped = $asn1->asn1map($decoded[0], $map);
1498 $subvalue = $mapped === false ?
$decoded[0] : $mapped;
1504 $value = base64_encode($value);
1511 * Map extension values from extension-specific internal format to
1514 * @param Array ref $root
1515 * @param String $path
1516 * @param Object $asn1
1519 function _mapOutExtensions(&$root, $path, $asn1)
1521 $extensions = &$this->_subArray($root, $path);
1523 if (is_array($extensions)) {
1524 $size = count($extensions);
1525 for ($i = 0; $i < $size; $i++
) {
1526 $id = $extensions[$i]['extnId'];
1527 $value = &$extensions[$i]['extnValue'];
1530 case 'id-ce-certificatePolicies':
1531 for ($j = 0; $j < count($value); $j++
) {
1532 if (!isset($value[$j]['policyQualifiers'])) {
1535 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++
) {
1536 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1537 $map = $this->_getMapping($subid);
1538 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1539 if ($map !== false) {
1540 // by default File_ASN1 will try to render qualifier as a FILE_ASN1_TYPE_IA5_STRING since it's
1541 // actual type is FILE_ASN1_TYPE_ANY
1542 $subvalue = new File_ASN1_Element($asn1->encodeDER($subvalue, $map));
1547 case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
1548 if (isset($value['authorityCertSerialNumber'])) {
1549 if ($value['authorityCertSerialNumber']->toBytes() == '') {
1550 $temp = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC
<< 6) |
2) . "\1\0";
1551 $value['authorityCertSerialNumber'] = new File_ASN1_Element($temp);
1556 /* [extnValue] contains the DER encoding of an ASN.1 value
1557 corresponding to the extension type identified by extnID */
1558 $map = $this->_getMapping($id);
1559 if (is_bool($map)) {
1561 user_error($id . ' is not a currently supported extension', E_USER_NOTICE
);
1562 unset($extensions[$i]);
1565 $temp = $asn1->encodeDER($value, $map);
1566 $value = base64_encode($temp);
1573 * Associate an extension ID to an extension mapping
1575 * @param String $extnId
1579 function _getMapping($extnId)
1582 case 'id-ce-keyUsage':
1583 return $this->KeyUsage
;
1584 case 'id-ce-basicConstraints':
1585 return $this->BasicConstraints
;
1586 case 'id-ce-subjectKeyIdentifier':
1587 return $this->KeyIdentifier
;
1588 case 'id-ce-cRLDistributionPoints':
1589 return $this->CRLDistributionPoints
;
1590 case 'id-ce-authorityKeyIdentifier':
1591 return $this->AuthorityKeyIdentifier
;
1592 case 'id-ce-certificatePolicies':
1593 return $this->CertificatePolicies
;
1594 case 'id-ce-extKeyUsage':
1595 return $this->ExtKeyUsageSyntax
;
1596 case 'id-pe-authorityInfoAccess':
1597 return $this->AuthorityInfoAccessSyntax
;
1598 case 'id-ce-subjectAltName':
1599 return $this->SubjectAltName
;
1600 case 'id-ce-privateKeyUsagePeriod':
1601 return $this->PrivateKeyUsagePeriod
;
1602 case 'id-ce-issuerAltName':
1603 return $this->IssuerAltName
;
1604 case 'id-ce-policyMappings':
1605 return $this->PolicyMappings
;
1606 case 'id-ce-nameConstraints':
1607 return $this->NameConstraints
;
1609 case 'netscape-cert-type':
1610 return $this->netscape_cert_type
;
1611 case 'netscape-comment':
1612 return $this->netscape_comment
;
1613 case 'netscape-ca-policy-url':
1614 return $this->netscape_ca_policy_url
;
1616 // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
1617 // back around to asn1map() and we don't want it decoded again.
1619 // return $this->CPSuri;
1620 case 'id-qt-unotice':
1621 return $this->UserNotice
;
1623 // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
1624 case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
1625 case 'entrustVersInfo':
1626 // http://support.microsoft.com/kb/287547
1627 case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
1628 case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
1629 // "SET Secure Electronic Transaction Specification"
1630 // http://www.maithean.com/docs/set_bk3.pdf
1631 case '2.23.42.7.0': // id-set-hashedRootKey
1635 case 'id-ce-cRLNumber':
1636 return $this->CRLNumber
;
1637 case 'id-ce-deltaCRLIndicator':
1638 return $this->CRLNumber
;
1639 case 'id-ce-issuingDistributionPoint':
1640 return $this->IssuingDistributionPoint
;
1641 case 'id-ce-freshestCRL':
1642 return $this->CRLDistributionPoints
;
1643 case 'id-ce-cRLReasons':
1644 return $this->CRLReason
;
1645 case 'id-ce-invalidityDate':
1646 return $this->InvalidityDate
;
1647 case 'id-ce-certificateIssuer':
1648 return $this->CertificateIssuer
;
1655 * Load an X.509 certificate as a certificate authority
1657 * @param String $cert
1661 function loadCA($cert)
1664 $oldcert = $this->currentCert
;
1665 $oldsigsubj = $this->signatureSubject
;
1667 $cert = $this->loadX509($cert);
1670 $this->currentCert
= $oldcert;
1671 $this->signatureSubject
= $oldsigsubj;
1676 /* From RFC5280 "PKIX Certificate and CRL Profile":
1678 If the keyUsage extension is present, then the subject public key
1679 MUST NOT be used to verify signatures on certificates or CRLs unless
1680 the corresponding keyCertSign or cRLSign bit is set. */
1681 //$keyUsage = $this->getExtension('id-ce-keyUsage');
1682 //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
1686 /* From RFC5280 "PKIX Certificate and CRL Profile":
1688 The cA boolean indicates whether the certified public key may be used
1689 to verify certificate signatures. If the cA boolean is not asserted,
1690 then the keyCertSign bit in the key usage extension MUST NOT be
1691 asserted. If the basic constraints extension is not present in a
1692 version 3 certificate, or the extension is present but the cA boolean
1693 is not asserted, then the certified public key MUST NOT be used to
1694 verify certificate signatures. */
1695 //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
1696 //if (!$basicConstraints || !$basicConstraints['cA']) {
1700 $this->CAs
[] = $cert;
1703 $this->currentCert
= $oldcert;
1704 $this->signatureSubject
= $oldsigsubj;
1710 * Validate an X.509 certificate against a URL
1712 * From RFC2818 "HTTP over TLS":
1714 * Matching is performed using the matching rules specified by
1715 * [RFC2459]. If more than one identity of a given type is present in
1716 * the certificate (e.g., more than one dNSName name, a match in any one
1717 * of the set is considered acceptable.) Names may contain the wildcard
1718 * character * which is considered to match any single domain name
1719 * component or component fragment. E.g., *.a.com matches foo.a.com but
1720 * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
1722 * @param String $url
1726 function validateURL($url)
1728 if (!is_array($this->currentCert
) ||
!isset($this->currentCert
['tbsCertificate'])) {
1732 $components = parse_url($url);
1733 if (!isset($components['host'])) {
1737 if ($names = $this->getExtension('id-ce-subjectAltName')) {
1738 foreach ($names as $key => $value) {
1739 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
1742 /* From RFC2818 "HTTP over TLS":
1744 If a subjectAltName extension of type dNSName is present, that MUST
1745 be used as the identity. Otherwise, the (most specific) Common Name
1746 field in the Subject field of the certificate MUST be used. Although
1747 the use of the Common Name is existing practice, it is deprecated and
1748 Certification Authorities are encouraged to use the dNSName instead. */
1749 if (preg_match('#^' . $value . '$#', $components['host'])) {
1754 /* From RFC2818 "HTTP over TLS":
1756 In some cases, the URI is specified as an IP address rather than a
1757 hostname. In this case, the iPAddress subjectAltName must be present
1758 in the certificate and must exactly match the IP in the URI. */
1759 if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
1767 if ($value = $this->getDNProp('id-at-commonName')) {
1768 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
1769 return preg_match('#^' . $value . '$#', $components['host']);
1778 * If $date isn't defined it is assumed to be the current date.
1780 * @param Integer $date optional
1783 function validateDate($date = NULL)
1785 if (!is_array($this->currentCert
) ||
!isset($this->currentCert
['tbsCertificate'])) {
1789 if (!isset($date)) {
1793 $notBefore = $this->currentCert
['tbsCertificate']['validity']['notBefore'];
1794 $notBefore = isset($notBefore['generalTime']) ?
$notBefore['generalTime'] : $notBefore['utcTime'];
1796 $notAfter = $this->currentCert
['tbsCertificate']['validity']['notAfter'];
1797 $notAfter = isset($notAfter['generalTime']) ?
$notAfter['generalTime'] : $notAfter['utcTime'];
1800 case $date < @strtotime
($notBefore):
1801 case $date > @strtotime
($notAfter):
1809 * Validate a signature
1811 * Works on X.509 certs, CSR's and CRL's.
1812 * Returns true if the signature is verified, false if it is not correct or NULL on error
1814 * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
1816 * @param Integer $options optional
1820 function validateSignature($options = 0)
1822 if (!is_array($this->currentCert
) ||
!isset($this->signatureSubject
)) {
1827 "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
1828 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
1830 implement pathLenConstraint in the id-ce-basicConstraints extension */
1833 case isset($this->currentCert
['tbsCertificate']):
1835 if ($this->currentCert
['tbsCertificate']['issuer'] === $this->currentCert
['tbsCertificate']['subject']) {
1836 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
1837 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
1839 case !is_array($authorityKey):
1840 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
1841 $signingCert = $this->currentCert
; // working cert
1845 if (!empty($this->CAs
)) {
1846 for ($i = 0; $i < count($this->CAs
); $i++
) {
1847 // even if the cert is a self-signed one we still want to see if it's a CA;
1848 // if not, we'll conditionally return an error
1849 $ca = $this->CAs
[$i];
1850 if ($this->currentCert
['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
1851 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
1852 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
1854 case !is_array($authorityKey):
1855 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
1856 $signingCert = $ca; // working cert
1861 if (count($this->CAs
) == $i && ($options & FILE_X509_VALIDATE_SIGNATURE_BY_CA
)) {
1864 } elseif (!isset($signingCert) ||
($options & FILE_X509_VALIDATE_SIGNATURE_BY_CA
)) {
1867 return $this->_validateSignature(
1868 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
1869 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
1870 $this->currentCert
['signatureAlgorithm']['algorithm'],
1871 substr(base64_decode($this->currentCert
['signature']), 1),
1872 $this->signatureSubject
1874 case isset($this->currentCert
['certificationRequestInfo']):
1875 return $this->_validateSignature(
1876 $this->currentCert
['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
1877 $this->currentCert
['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
1878 $this->currentCert
['signatureAlgorithm']['algorithm'],
1879 substr(base64_decode($this->currentCert
['signature']), 1),
1880 $this->signatureSubject
1882 case isset($this->currentCert
['tbsCertList']):
1883 if (!empty($this->CAs
)) {
1884 for ($i = 0; $i < count($this->CAs
); $i++
) {
1885 $ca = $this->CAs
[$i];
1886 if ($this->currentCert
['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']) {
1887 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
1888 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
1890 case !is_array($authorityKey):
1891 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
1892 $signingCert = $ca; // working cert
1898 if (!isset($signingCert)) {
1901 return $this->_validateSignature(
1902 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
1903 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
1904 $this->currentCert
['signatureAlgorithm']['algorithm'],
1905 substr(base64_decode($this->currentCert
['signature']), 1),
1906 $this->signatureSubject
1914 * Validates a signature
1916 * Returns true if the signature is verified, false if it is not correct or NULL on error
1918 * @param String $publicKeyAlgorithm
1919 * @param String $publicKey
1920 * @param String $signatureAlgorithm
1921 * @param String $signature
1922 * @param String $signatureSubject
1926 function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
1928 switch ($publicKeyAlgorithm) {
1929 case 'rsaEncryption':
1930 if (!class_exists('Crypt_RSA')) {
1931 require_once('Crypt/RSA.php');
1933 $rsa = new Crypt_RSA();
1934 $rsa->loadKey($publicKey);
1936 switch ($signatureAlgorithm) {
1937 case 'md2WithRSAEncryption':
1938 case 'md5WithRSAEncryption':
1939 case 'sha1WithRSAEncryption':
1940 case 'sha224WithRSAEncryption':
1941 case 'sha256WithRSAEncryption':
1942 case 'sha384WithRSAEncryption':
1943 case 'sha512WithRSAEncryption':
1944 $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
1945 $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1
);
1947 if (!@$rsa->verify($signatureSubject, $signature)) {
1963 * Reformat public keys
1965 * Reformats a public key to a format supported by phpseclib (if applicable)
1967 * @param String $algorithm
1968 * @param String $key
1972 function _reformatKey($algorithm, $key)
1974 switch ($algorithm) {
1975 case 'rsaEncryption':
1977 "-----BEGIN PUBLIC KEY-----\r\n" .
1978 // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits
1979 // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox
1980 // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
1981 chunk_split(base64_encode(substr(base64_decode($key), 1))) .
1982 '-----END PUBLIC KEY-----';
1989 * "Normalizes" a Distinguished Name property
1991 * @param String $propName
1995 function _translateDNProp($propName)
1997 switch (strtolower($propName)) {
1998 case 'id-at-countryname':
2001 return 'id-at-countryName';
2002 case 'id-at-organizationname':
2003 case 'organizationname':
2005 return 'id-at-organizationName';
2006 case 'id-at-dnqualifier':
2008 return 'id-at-dnQualifier';
2009 case 'id-at-commonname':
2012 return 'id-at-commonName';
2013 case 'id-at-stateorprovinceName':
2014 case 'stateorprovincename':
2017 case 'provincename':
2019 return 'id-at-stateOrProvinceName';
2020 case 'id-at-localityname':
2021 case 'localityname':
2023 return 'id-at-localityName';
2024 case 'id-emailaddress':
2025 case 'emailaddress':
2026 return 'id-emailAddress';
2027 case 'id-at-serialnumber':
2028 case 'serialnumber':
2029 return 'id-at-serialNumber';
2030 case 'id-at-postalcode':
2032 return 'id-at-postalCode';
2033 case 'id-at-streetaddress':
2034 case 'streetaddress':
2035 return 'id-at-streetAddress';
2038 return 'id-at-name';
2039 case 'id-at-givenname':
2041 return 'id-at-givenName';
2042 case 'id-at-surname':
2045 return 'id-at-surname';
2046 case 'id-at-initials':
2048 return 'id-at-initials';
2049 case 'id-at-generationqualifier':
2050 case 'generationqualifier':
2051 return 'id-at-generationQualifier';
2052 case 'id-at-organizationalunitname':
2053 case 'organizationalunitname':
2055 return 'id-at-organizationalUnitName';
2056 case 'id-at-pseudonym':
2058 return 'id-at-pseudonym';
2061 return 'id-at-title';
2062 case 'id-at-description':
2064 return 'id-at-description';
2067 return 'id-at-role';
2068 case 'id-at-uniqueidentifier':
2069 case 'uniqueidentifier':
2070 case 'x500uniqueidentifier':
2071 return 'id-at-uniqueIdentifier';
2078 * Set a Distinguished Name property
2080 * @param String $propName
2081 * @param Mixed $propValue
2082 * @param String $type optional
2086 function setDNProp($propName, $propValue, $type = 'utf8String')
2088 if (empty($this->dn
)) {
2089 $this->dn
= array('rdnSequence' => array());
2092 if (($propName = $this->_translateDNProp($propName)) === false) {
2096 foreach ((array) $propValue as $v) {
2097 if (!is_array($v) && isset($type)) {
2098 $v = array($type => $v);
2100 $this->dn
['rdnSequence'][] = array(
2102 'type' => $propName,
2112 * Remove Distinguished Name properties
2114 * @param String $propName
2117 function removeDNProp($propName)
2119 if (empty($this->dn
)) {
2123 if (($propName = $this->_translateDNProp($propName)) === false) {
2127 $dn = &$this->dn
['rdnSequence'];
2129 for ($i = 0; $i < $size; $i++
) {
2130 if ($dn[$i][0]['type'] == $propName) {
2135 $dn = array_values($dn);
2139 * Get Distinguished Name properties
2141 * @param String $propName
2142 * @param Array $dn optional
2143 * @param Boolean $withType optional
2147 function getDNProp($propName, $dn = NULL, $withType = false)
2157 if (($propName = $this->_translateDNProp($propName)) === false) {
2161 $dn = $dn['rdnSequence'];
2163 $asn1 = new File_ASN1();
2164 for ($i = 0; $i < count($dn); $i++
) {
2165 if ($dn[$i][0]['type'] == $propName) {
2166 $v = $dn[$i][0]['value'];
2167 if (!$withType && is_array($v)) {
2168 foreach ($v as $type => $s) {
2169 $type = array_search($type, $asn1->ANYmap
, true);
2170 if ($type !== false && isset($asn1->stringTypeSize
[$type])) {
2171 $s = $asn1->convert($s, $type);
2179 $v = array_pop($v); // Always strip data type.
2190 * Set a Distinguished Name
2193 * @param Boolean $merge optional
2194 * @param String $type optional
2198 function setDN($dn, $merge = false, $type = 'utf8String')
2204 if (is_array($dn)) {
2205 if (isset($dn['rdnSequence'])) {
2206 $this->dn
= $dn; // No merge here.
2210 // handles stuff generated by openssl_x509_parse()
2211 foreach ($dn as $prop => $value) {
2212 if (!$this->setDNProp($prop, $value, $type)) {
2219 // handles everything else
2220 $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
);
2221 for ($i = 1; $i < count($results); $i+
=2) {
2222 $prop = trim($results[$i], ', =/');
2223 $value = $results[$i +
1];
2224 if (!$this->setDNProp($prop, $value, $type)) {
2233 * Get the Distinguished Name for a certificates subject
2235 * @param Mixed $format optional
2236 * @param Array $dn optional
2240 function getDN($format = FILE_X509_DN_ARRAY
, $dn = NULL)
2243 $dn = isset($this->currentCert
['tbsCertList']) ?
$this->currentCert
['tbsCertList']['issuer'] : $this->dn
;
2246 switch ((int) $format) {
2247 case FILE_X509_DN_ARRAY
:
2249 case FILE_X509_DN_ASN1
:
2250 $asn1 = new File_ASN1();
2251 $asn1->loadOIDs($this->oids
);
2253 $filters['rdnSequence']['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING
);
2254 $asn1->loadFilters($filters);
2255 return $asn1->encodeDER($dn, $this->Name
);
2256 case FILE_X509_DN_OPENSSL
:
2257 $dn = $this->getDN(FILE_X509_DN_STRING
, $dn);
2258 if ($dn === false) {
2261 $attrs = preg_split('#((?:^|, *|/)[a-z][a-z0-9]*=)#i', $dn, -1, PREG_SPLIT_DELIM_CAPTURE
);
2263 for ($i = 1; $i < count($attrs); $i +
= 2) {
2264 $prop = trim($attrs[$i], ', =/');
2265 $value = $attrs[$i +
1];
2266 if (!isset($dn[$prop])) {
2267 $dn[$prop] = $value;
2269 $dn[$prop] = array_merge((array) $dn[$prop], array($value));
2273 case FILE_X509_DN_CANON
:
2274 // No SEQUENCE around RDNs and all string values normalized as
2275 // trimmed lowercase UTF-8 with all spacing as one blank.
2276 $asn1 = new File_ASN1();
2277 $asn1->loadOIDs($this->oids
);
2279 $filters['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING
);
2280 $asn1->loadFilters($filters);
2282 foreach ($dn['rdnSequence'] as $rdn) {
2283 foreach ($rdn as &$attr) {
2284 if (is_array($attr['value'])) {
2285 foreach ($attr['value'] as $type => $v) {
2286 $type = array_search($type, $asn1->ANYmap
, true);
2287 if ($type !== false && isset($asn1->stringTypeSize
[$type])) {
2288 $v = $asn1->convert($v, $type);
2290 $v = preg_replace('/\s+/', ' ', $v);
2291 $attr['value'] = strtolower(trim($v));
2298 $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName
);
2301 case FILE_X509_DN_HASH
:
2302 $dn = $this->getDN(FILE_X509_DN_CANON
, $dn);
2303 if (!class_exists('Crypt_Hash')) {
2304 require_once('Crypt/Hash.php');
2306 $hash = new Crypt_Hash('sha1');
2307 $hash = $hash->hash($dn);
2308 extract(unpack('Vhash', $hash));
2309 return strtolower(bin2hex(pack('N', $hash)));
2312 // Defaut is to return a string.
2315 $asn1 = new File_ASN1();
2316 foreach ($dn['rdnSequence'] as $field) {
2317 $prop = $field[0]['type'];
2318 $value = $field[0]['value'];
2322 case 'id-at-countryName':
2325 case 'id-at-stateOrProvinceName':
2328 case 'id-at-organizationName':
2331 case 'id-at-organizationalUnitName':
2334 case 'id-at-commonName':
2337 case 'id-at-localityName':
2340 case 'id-at-surname':
2343 case 'id-at-uniqueIdentifier':
2345 $desc = 'x500UniqueIdentifier=';
2349 $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop) . '=';
2355 if (is_array($value)) {
2356 foreach ($value as $type => $v) {
2357 $type = array_search($type, $asn1->ANYmap
, true);
2358 if ($type !== false && isset($asn1->stringTypeSize
[$type])) {
2359 $v = $asn1->convert($v, $type);
2366 if (is_array($value)) {
2367 $value = array_pop($value); // Always strip data type.
2370 $output.= $desc . $value;
2378 * Get the Distinguished Name for a certificate/crl issuer
2380 * @param Integer $format optional
2384 function getIssuerDN($format = FILE_X509_DN_ARRAY
)
2387 case !isset($this->currentCert
) ||
!is_array($this->currentCert
):
2389 case isset($this->currentCert
['tbsCertificate']):
2390 return $this->getDN($format, $this->currentCert
['tbsCertificate']['issuer']);
2391 case isset($this->currentCert
['tbsCertList']):
2392 return $this->getDN($format, $this->currentCert
['tbsCertList']['issuer']);
2399 * Get the Distinguished Name for a certificate/csr subject
2402 * @param Integer $format optional
2406 function getSubjectDN($format = FILE_X509_DN_ARRAY
)
2409 case !empty($this->dn
):
2410 return $this->getDN($format);
2411 case !isset($this->currentCert
) ||
!is_array($this->currentCert
):
2413 case isset($this->currentCert
['tbsCertificate']):
2414 return $this->getDN($format, $this->currentCert
['tbsCertificate']['subject']);
2415 case isset($this->currentCert
['certificationRequestInfo']):
2416 return $this->getDN($format, $this->currentCert
['certificationRequestInfo']['subject']);
2423 * Get an individual Distinguished Name property for a certificate/crl issuer
2425 * @param String $propName
2426 * @param Boolean $withType optional
2430 function getIssuerDNProp($propName, $withType = false)
2433 case !isset($this->currentCert
) ||
!is_array($this->currentCert
):
2435 case isset($this->currentCert
['tbsCertificate']):
2436 return $this->getDNProp($propname, $this->currentCert
['tbsCertificate']['issuer'], $withType);
2437 case isset($this->currentCert
['tbsCertList']):
2438 return $this->getDNProp($propname, $this->currentCert
['tbsCertList']['issuer'], $withType);
2445 * Get an individual Distinguished Name property for a certificate/csr subject
2447 * @param String $propName
2448 * @param Boolean $withType optional
2452 function getSubjectDNProp($propName, $withType = false)
2455 case !empty($this->dn
):
2456 return $this->getDNProp($propName, NULL, $withType);
2457 case !isset($this->currentCert
) ||
!is_array($this->currentCert
):
2459 case isset($this->currentCert
['tbsCertificate']):
2460 return $this->getDNProp($propName, $this->currentCert
['tbsCertificate']['subject'], $withType);
2461 case isset($this->currentCert
['certificationRequestInfo']):
2462 return $this->getDNProp($propname, $this->currentCert
['certificationRequestInfo']['subject'], $withType);
2471 * Key needs to be a Crypt_RSA object
2473 * @param Object $key
2477 function setPublicKey($key)
2479 $this->publicKey
= $key;
2485 * Key needs to be a Crypt_RSA object
2487 * @param Object $key
2490 function setPrivateKey($key)
2492 $this->privateKey
= $key;
2496 * Gets the public key
2498 * Returns a Crypt_RSA object or a false.
2503 function getPublicKey()
2505 if (isset($this->publicKey
)) {
2506 return $this->publicKey
;
2509 if (isset($this->currentCert
) && is_array($this->currentCert
)) {
2510 foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
2511 $keyinfo = $this->_subArray($this->currentCert
, $path);
2512 if (!empty($keyinfo)) {
2517 if (empty($keyinfo)) {
2521 $key = $keyinfo['subjectPublicKey'];
2523 switch ($keyinfo['algorithm']['algorithm']) {
2524 case 'rsaEncryption':
2525 if (!class_exists('Crypt_RSA')) {
2526 require_once('Crypt/RSA.php');
2528 $publicKey = new Crypt_RSA();
2529 $publicKey->loadKey($key);
2530 $publicKey->setPublicKey();
2540 * Load a Certificate Signing Request
2542 * @param String $csr
2546 function loadCSR($csr)
2548 // see http://tools.ietf.org/html/rfc2986
2550 $asn1 = new File_ASN1();
2552 $temp = preg_replace('#^(?:[^-].+[\r\n]+)+|-.+-|[\r\n]| #', '', $csr);
2553 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ?
base64_decode($temp) : false;
2554 if ($temp != false) {
2559 if ($csr === false) {
2560 $this->currentCert
= false;
2564 $asn1->loadOIDs($this->oids
);
2565 $decoded = $asn1->decodeBER($csr);
2567 if (empty($decoded)) {
2568 $this->currentCert
= false;
2572 $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest
);
2573 if (!isset($csr) ||
$csr === false) {
2574 $this->currentCert
= false;
2578 $this->dn
= $csr['certificationRequestInfo']['subject'];
2580 $this->signatureSubject
= substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
2582 $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
2583 $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
2584 $key = $this->_reformatKey($algorithm, $key);
2586 switch ($algorithm) {
2587 case 'rsaEncryption':
2588 if (!class_exists('Crypt_RSA')) {
2589 require_once('Crypt/RSA.php');
2591 $this->publicKey
= new Crypt_RSA();
2592 $this->publicKey
->loadKey($key);
2593 $this->publicKey
->setPublicKey();
2596 $this->publicKey
= NULL;
2599 $this->currentKeyIdentifier
= NULL;
2600 $this->currentCert
= $csr;
2612 function saveCSR($csr)
2614 if (!is_array($csr) ||
!isset($csr['certificationRequestInfo'])) {
2618 switch ($csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']) {
2619 case 'rsaEncryption':
2620 $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] =
2621 base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
2624 $asn1 = new File_ASN1();
2626 $asn1->loadOIDs($this->oids
);
2629 $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] =
2630 array('type' => FILE_ASN1_TYPE_UTF8_STRING
);
2632 $asn1->loadFilters($filters);
2634 $csr = $asn1->encodeDER($csr, $this->CertificationRequest
);
2636 return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr)) . '-----END CERTIFICATE REQUEST-----';
2640 * Load a Certificate Revocation List
2642 * @param String $crl
2646 function loadCRL($crl)
2648 $asn1 = new File_ASN1();
2650 $temp = preg_replace('#^(?:[^-].+[\r\n]+)+|-.+-|[\r\n]| #', '', $csr);
2651 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ?
base64_decode($temp) : false;
2652 if ($temp != false) {
2657 if ($crl === false) {
2658 $this->currentCert
= false;
2662 $asn1->loadOIDs($this->oids
);
2663 $decoded = $asn1->decodeBER($crl);
2665 if (empty($decoded)) {
2666 $this->currentCert
= false;
2670 $crl = $asn1->asn1map($decoded[0], $this->CertificateList
);
2671 if (!isset($crl) ||
$crl === false) {
2672 $this->currentCert
= false;
2676 $this->signatureSubject
= substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
2678 $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
2679 $rclist = &$this->_subArray($crl,'tbsCertList/revokedCertificates');
2680 if (is_array($rclist)) {
2681 foreach ($rclist as $i => $extension) {
2682 $this->_mapInExtensions($rclist, "$i/crlEntryExtensions", $asn1);
2686 $this->currentKeyIdentifier
= NULL;
2687 $this->currentCert
= $crl;
2693 * Save Certificate Revocation List.
2699 function saveCRL($crl)
2701 if (!is_array($crl) ||
!isset($crl['tbsCertList'])) {
2705 $asn1 = new File_ASN1();
2707 $asn1->loadOIDs($this->oids
);
2710 $filters['tbsCertList']['issuer']['rdnSequence']['value'] =
2711 $filters['tbsCertList']['signature']['parameters'] =
2712 $filters['signatureAlgorithm']['parameters'] =
2713 array('type' => FILE_ASN1_TYPE_UTF8_STRING
);
2715 if (empty($crl['tbsCertList']['signature']['parameters'])) {
2716 $filters['tbsCertList']['signature']['parameters'] =
2717 array('type' => FILE_ASN1_TYPE_NULL
);
2720 if (empty($crl['signatureAlgorithm']['parameters'])) {
2721 $filters['signatureAlgorithm']['parameters'] =
2722 array('type' => FILE_ASN1_TYPE_NULL
);
2725 $asn1->loadFilters($filters);
2727 $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
2728 $rclist = &$this->_subArray($crl,'tbsCertList/revokedCertificates');
2729 if (is_array($rclist)) {
2730 foreach ($rclist as $i => $extension) {
2731 $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
2735 $crl = $asn1->encodeDER($crl, $this->CertificateList
);
2737 return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl)) . '-----END X509 CRL-----';
2741 * Sign an X.509 certificate
2743 * $issuer's private key needs to be loaded.
2744 * $subject can be either an existing X.509 cert (if you want to resign it),
2745 * a CSR or something with the DN and public key explicitly set.
2747 * @param File_X509 $issuer
2748 * @param File_X509 $subject
2749 * @param String $signatureAlgorithm optional
2753 function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
2755 if (!is_object($issuer->privateKey
) ||
empty($issuer->dn
)) {
2759 if (isset($subject->publicKey
) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
2763 $currentCert = isset($this->currentCert
) ?
$this->currentCert
: NULL;
2764 $signatureSubject = isset($this->signatureSubject
) ?
$this->signatureSubject
: NULL;
2766 if (isset($subject->currentCert
) && is_array($subject->currentCert
) && isset($subject->currentCert
['tbsCertificate'])) {
2767 $this->currentCert
= $subject->currentCert
;
2768 $this->currentCert
['tbsCertificate']['signature']['algorithm'] =
2769 $this->currentCert
['signatureAlgorithm']['algorithm'] =
2770 $signatureAlgorithm;
2771 if (!empty($this->startDate
)) {
2772 $this->currentCert
['tbsCertificate']['validity']['notBefore']['generalTime'] = $this->startDate
;
2773 unset($this->currentCert
['tbsCertificate']['validity']['notBefore']['utcTime']);
2775 if (!empty($this->endDate
)) {
2776 $this->currentCert
['tbsCertificate']['validity']['notAfter']['generalTime'] = $this->endDate
;
2777 unset($this->currentCert
['tbsCertificate']['validity']['notAfter']['utcTime']);
2779 if (!empty($this->serialNumber
)) {
2780 $this->currentCert
['tbsCertificate']['serialNumber'] = $this->serialNumber
;
2782 if (!empty($subject->dn
)) {
2783 $this->currentCert
['tbsCertificate']['subject'] = $subject->dn
;
2785 if (!empty($subject->publicKey
)) {
2786 $this->currentCert
['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
2788 $this->removeExtension('id-ce-authorityKeyIdentifier');
2789 if (isset($subject->domains
)) {
2790 $this->removeExtension('id-ce-subjectAltName');
2792 } else if (isset($subject->currentCert
) && is_array($subject->currentCert
) && isset($subject->currentCert
['tbsCertList'])) {
2795 if (!isset($subject->publicKey
)) {
2799 $startDate = !empty($this->startDate
) ?
$this->startDate
: @date
('M j H:i:s Y T');
2800 $endDate = !empty($this->endDate
) ?
$this->endDate
: @date
('M j H:i:s Y T', strtotime('+1 year'));
2801 $serialNumber = !empty($this->serialNumber
) ?
$this->serialNumber
: new Math_BigInteger();
2803 $this->currentCert
= array(
2807 'serialNumber' => $serialNumber, // $this->setserialNumber()
2808 'signature' => array('algorithm' => $signatureAlgorithm),
2809 'issuer' => false, // this is going to be overwritten later
2810 'validity' => array(
2811 'notBefore' => array('generalTime' => $startDate), // $this->setStartDate()
2812 'notAfter' => array('generalTime' => $endDate) // $this->setEndDate()
2814 'subject' => $subject->dn
,
2815 'subjectPublicKeyInfo' => $subjectPublicKey
2817 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
2818 'signature' => false // this is going to be overwritten later
2822 $this->currentCert
['tbsCertificate']['issuer'] = $issuer->dn
;
2824 if (isset($issuer->currentKeyIdentifier
)) {
2825 $this->setExtension('id-ce-authorityKeyIdentifier', array(
2826 //'authorityCertIssuer' => array(
2828 // 'directoryName' => $issuer->dn
2831 'keyIdentifier' => $issuer->currentKeyIdentifier
2834 //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
2835 //if (isset($issuer->serialNumber)) {
2836 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
2838 //unset($extensions);
2841 if (isset($subject->currentKeyIdentifier
)) {
2842 $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier
);
2845 if (isset($subject->domains
) && count($subject->domains
) > 1) {
2846 $this->setExtension('id-ce-subjectAltName',
2847 array_map(array('File_X509', '_dnsName'), $subject->domains
));
2850 if ($this->caFlag
) {
2851 $keyUsage = $this->getExtension('id-ce-keyUsage');
2853 $keyUsage = array();
2856 $this->setExtension('id-ce-keyUsage',
2857 array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
2860 $basicConstraints = $this->getExtension('id-ce-basicConstraints');
2861 if (!$basicConstraints) {
2862 $basicConstraints = array();
2865 $this->setExtension('id-ce-basicConstraints',
2866 array_unique(array_merge(array('cA' => true), $basicConstraints)), true);
2868 if (!isset($subject->currentKeyIdentifier
)) {
2869 $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert
)), false, false);
2873 // resync $this->signatureSubject
2874 // save $tbsCertificate in case there are any File_ASN1_Element objects in it
2875 $tbsCertificate = $this->currentCert
['tbsCertificate'];
2876 $this->loadX509($this->saveX509($this->currentCert
));
2878 $result = $this->_sign($issuer->privateKey
, $signatureAlgorithm);
2879 $result['tbsCertificate'] = $tbsCertificate;
2881 $this->currentCert
= $currentCert;
2882 $this->signatureSubject
= $signatureSubject;
2893 function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
2895 if (!is_object($this->privateKey
) ||
empty($this->dn
)) {
2899 $origPublicKey = $this->publicKey
;
2900 $class = get_class($this->privateKey
);
2901 $this->publicKey
= new $class();
2902 $this->publicKey
->loadKey($this->privateKey
->getPublicKey());
2903 $this->publicKey
->setPublicKey();
2904 if (!($publicKey = $this->_formatSubjectPublicKey())) {
2907 $this->publicKey
= $origPublicKey;
2909 $currentCert = isset($this->currentCert
) ?
$this->currentCert
: NULL;
2910 $signatureSubject = isset($this->signatureSubject
) ?
$this->signatureSubject
: NULL;
2912 if (isset($this->currentCert
) && is_array($this->currentCert
) && isset($this->currentCert
['certificationRequestInfo'])) {
2913 $this->currentCert
['signatureAlgorithm']['algorithm'] =
2914 $signatureAlgorithm;
2915 if (!empty($this->dn
)) {
2916 $this->currentCert
['certificationRequestInfo']['subject'] = $this->dn
;
2918 $this->currentCert
['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
2920 $this->currentCert
= array(
2921 'certificationRequestInfo' =>
2924 'subject' => $this->dn
,
2925 'subjectPKInfo' => $publicKey
2927 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
2928 'signature' => false // this is going to be overwritten later
2932 // resync $this->signatureSubject
2933 // save $certificationRequestInfo in case there are any File_ASN1_Element objects in it
2934 $certificationRequestInfo = $this->currentCert
['certificationRequestInfo'];
2935 $this->loadCSR($this->saveCSR($this->currentCert
));
2937 $result = $this->_sign($this->privateKey
, $signatureAlgorithm);
2938 $result['certificationRequestInfo'] = $certificationRequestInfo;
2940 $this->currentCert
= $currentCert;
2941 $this->signatureSubject
= $signatureSubject;
2949 * $issuer's private key needs to be loaded.
2951 * @param File_X509 $issuer
2952 * @param File_X509 $crl
2953 * @param String $signatureAlgorithm optional
2957 function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
2959 if (!is_object($issuer->privateKey
) ||
empty($issuer->dn
)) {
2963 $currentCert = isset($this->currentCert
) ?
$this->currentCert
: NULL;
2964 $signatureSubject = isset($this->signatureSubject
) ?
$this->signatureSubject
: NULL;
2965 $thisUpdate = !empty($this->startDate
) ?
$this->startDate
: @date
('M j H:i:s Y T');
2967 if (isset($crl->currentCert
) && is_array($crl->currentCert
) && isset($crl->currentCert
['tbsCertList'])) {
2968 $this->currentCert
= $crl->currentCert
;
2969 $this->currentCert
['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
2970 $this->currentCert
['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
2972 $this->currentCert
= array(
2976 'signature' => array('algorithm' => $signatureAlgorithm),
2977 'issuer' => false, // this is going to be overwritten later
2978 'thisUpdate' => array('generalTime' => $thisUpdate) // $this->setStartDate()
2980 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
2981 'signature' => false // this is going to be overwritten later
2985 $tbsCertList = &$this->currentCert
['tbsCertList'];
2986 $tbsCertList['issuer'] = $issuer->dn
;
2987 $tbsCertList['thisUpdate'] = array('generalTime' => $thisUpdate);
2989 if (!empty($this->endDate
)) {
2990 $tbsCertList['nextUpdate'] = array('generalTime' => $this->endDate
); // $this->setEndDate()
2993 unset($tbsCertList['nextUpdate']);
2996 if (!empty($this->serialNumber
)) {
2997 $crlNumber = $this->serialNumber
;
3000 $crlNumber = $this->getExtension('id-ce-cRLNumber');
3001 $crlNumber = $crlNumber !== false ?
$crlNumber->add(new Math_BigInteger(1)) : NULL;
3004 $this->removeExtension('id-ce-authorityKeyIdentifier');
3005 $this->removeExtension('id-ce-issuerAltName');
3007 // Be sure version >= v2 if some extension found.
3008 $version = isset($tbsCertList['version']) ?
$tbsCertList['version'] : 0;
3010 if (!empty($tbsCertList['crlExtensions'])) {
3011 $version = 1; // v2.
3013 elseif (!empty($tbsCertList['revokedCertificates'])) {
3014 foreach ($tbsCertList['revokedCertificates'] as $cert) {
3015 if (!empty($cert['crlEntryExtensions'])) {
3016 $version = 1; // v2.
3022 $tbsCertList['version'] = $version;
3026 // Store additional extensions.
3027 if (!empty($tbsCertList['version'])) { // At least v2.
3028 if (!empty($crlNumber)) {
3029 $this->setExtension('id-ce-cRLNumber', $crlNumber);
3032 if (isset($issuer->currentKeyIdentifier
)) {
3033 $this->setExtension('id-ce-authorityKeyIdentifier', array(
3034 //'authorityCertIssuer' => array(
3036 // 'directoryName' => $issuer->dn
3039 'keyIdentifier' => $issuer->currentKeyIdentifier
3042 //$extensions = &$tbsCertList['crlExtensions'];
3043 //if (isset($issuer->serialNumber)) {
3044 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3046 //unset($extensions);
3049 $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert
);
3051 if ($issuerAltName !== false) {
3052 $this->setExtension('id-ce-issuerAltName', $issuerAltName);
3056 if (empty($tbsCertList['revokedCertificates'])) {
3057 unset($tbsCertList['revokedCertificates']);
3060 unset($tbsCertList);
3062 // resync $this->signatureSubject
3063 // save $tbsCertList in case there are any File_ASN1_Element objects in it
3064 $tbsCertList = $this->currentCert
['tbsCertList'];
3065 $this->loadCRL($this->saveCRL($this->currentCert
));
3067 $result = $this->_sign($issuer->privateKey
, $signatureAlgorithm);
3068 $result['tbsCertList'] = $tbsCertList;
3070 $this->currentCert
= $currentCert;
3071 $this->signatureSubject
= $signatureSubject;
3077 * X.509 certificate signing helper function.
3079 * @param Object $key
3080 * @param File_X509 $subject
3081 * @param String $signatureAlgorithm
3085 function _sign($key, $signatureAlgorithm)
3087 switch (strtolower(get_class($key))) {
3089 switch ($signatureAlgorithm) {
3090 case 'md2WithRSAEncryption':
3091 case 'md5WithRSAEncryption':
3092 case 'sha1WithRSAEncryption':
3093 case 'sha224WithRSAEncryption':
3094 case 'sha256WithRSAEncryption':
3095 case 'sha384WithRSAEncryption':
3096 case 'sha512WithRSAEncryption':
3097 $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
3098 $key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1
);
3100 $this->currentCert
['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject
));
3101 return $this->currentCert
;
3109 * Set certificate start date
3111 * @param String $date
3114 function setStartDate($date)
3116 $this->startDate
= @date
('M j H:i:s Y T', @strtotime
($date));
3120 * Set certificate end date
3122 * @param String $date
3125 function setEndDate($date)
3128 To indicate that a certificate has no well-defined expiration date,
3129 the notAfter SHOULD be assigned the GeneralizedTime value of
3132 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
3134 if (strtolower($date) == 'lifetime') {
3135 $temp = '99991231235959Z';
3136 $asn1 = new File_ASN1();
3137 $temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME
) . $asn1->_encodeLength(strlen($temp)) . $temp;
3138 $this->endDate
= new File_ASN1_Element($temp);
3140 $this->endDate
= @date
('M j H:i:s Y T', @strtotime
($date));
3147 * @param String $serial
3148 * @param $base optional
3151 function setSerialNumber($serial, $base = -256)
3153 $this->serialNumber
= new Math_BigInteger($serial, $base);
3157 * Turns the certificate into a certificate authority
3163 $this->caFlag
= true;
3167 * Get a reference to a subarray
3169 * @param array $root
3170 * @param String $path absolute path with / as component separator
3171 * @param Boolean $create optional
3173 * @return array item ref or false
3175 function &_subArray(&$root, $path, $create = false)
3179 if (!is_array($root)) {
3183 foreach (explode('/', $path) as $i) {
3184 if (!is_array($root)) {
3188 if (!isset($root[$i])) {
3193 $root[$i] = array();
3203 * Get a reference to an extension subarray
3205 * @param array $root
3206 * @param String $path optional absolute path with / as component separator
3207 * @param Boolean $create optional
3209 * @return array ref or false
3211 function &_extensions(&$root, $path = NULL, $create = false)
3213 if (!isset($root)) {
3214 $root = $this->currentCert
;
3219 case !is_array($root):
3221 case isset($root['tbsCertificate']):
3222 $path = 'tbsCertificate/extensions';
3224 case isset($root['tbsCertList']):
3225 $path = 'tbsCertList/crlExtensions';
3229 $extensions = &$this->_subArray($root, $path, $create);
3231 if (!is_array($extensions)) {
3240 * Remove an Extension
3243 * @param String $path optional
3247 function _removeExtension($id, $path = NULL)
3249 $extensions = &$this->_extensions($this->currentCert
, $path);
3251 if (!is_array($extensions)) {
3256 foreach ($extensions as $key => $value) {
3257 if ($value['extnId'] == $id) {
3258 unset($extensions[$key]);
3263 $extensions = array_values($extensions);
3270 * Returns the extension if it exists and false if not
3273 * @param Array $cert optional
3274 * @param String $path optional
3278 function _getExtension($id, $cert = NULL, $path = NULL)
3280 $extensions = $this->_extensions($cert, $path);
3282 if (!is_array($extensions)) {
3286 foreach ($extensions as $key => $value) {
3287 if ($value['extnId'] == $id) {
3288 return $value['extnValue'];
3296 * Returns a list of all extensions in use
3298 * @param array $cert optional
3299 * @param String $path optional
3303 function _getExtensions($cert = NULL, $path = NULL)
3305 $exts = $this->_extensions($cert, $path);
3306 $extensions = array();
3308 if (is_array($exts)) {
3309 foreach ($exts as $extension) {
3310 $extensions[] = $extension['extnId'];
3321 * @param Mixed $value
3322 * @param Boolean $critical optional
3323 * @param Boolean $replace optional
3324 * @param String $path optional
3328 function _setExtension($id, $value, $critical = false, $replace = true, $path = NULL)
3330 $extensions = &$this->_extensions($this->currentCert
, $path, true);
3332 if (!is_array($extensions)) {
3336 $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value);
3338 foreach ($extensions as $key => $value) {
3339 if ($value['extnId'] == $id) {
3344 $extensions[$key] = $newext;
3349 $extensions[] = $newext;
3354 * Remove a certificate or CRL Extension
3360 function removeExtension($id)
3362 return $this->_removeExtension($id);
3366 * Get a certificate or CRL Extension
3368 * Returns the extension if it exists and false if not
3371 * @param Array $cert optional
3375 function getExtension($id, $cert = NULL)
3377 return $this->_getExtension($id, $cert);
3381 * Returns a list of all extensions in use in certificate or CRL
3383 * @param array $cert optional
3387 function getExtensions($cert = NULL)
3389 return $this->_getExtensions($cert);
3393 * Set a certificate or CRL Extension
3396 * @param Mixed $value
3397 * @param Boolean $critical optional
3398 * @param Boolean $replace optional
3402 function setExtension($id, $value, $critical = false, $replace = true)
3404 return $this->_setExtension($id, $value, $critical, $replace);
3408 * Sets the subject key identifier
3410 * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
3412 * @param String $value
3415 function setKeyIdentifier($value)
3417 if (empty($value)) {
3418 unset($this->currentKeyIdentifier
);
3420 $this->currentKeyIdentifier
= base64_encode($value);
3425 * Compute a public key identifier.
3427 * Although key identifiers may be set to any unique value, this function
3428 * computes key identifiers from public key according to the two
3429 * recommended methods (4.2.1.2 RFC 3280).
3430 * Highly polymorphic: try to accept all possible forms of key:
3432 * - File_X509 object with public or private key defined
3433 * - Certificate or CSR array
3434 * - File_ASN1_Element object
3435 * - PEM or DER string
3437 * @param Mixed $key optional
3438 * @param Integer $method optional
3440 * @return String binary key identifier
3442 function computeKeyIdentifier($key = NULL, $method = 1)
3444 if (is_null($key)) {
3449 case is_string($key):
3451 case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
3452 return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
3453 case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
3454 return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
3455 case !is_object($key):
3457 case strtolower(get_class($key)) == 'file_asn1_element':
3458 $asn1 = new File_ASN1();
3459 $decoded = $asn1->decodeBER($cert);
3460 if (empty($decoded)) {
3463 $key = $asn1->asn1map($decoded[0], array('type' => FILE_ASN1_TYPE_BIT_STRING
));
3465 case strtolower(get_class($key)) == 'file_x509':
3466 if (isset($key->publicKey
)) {
3467 return $this->computeKeyIdentifier($key->publicKey
, $method);
3469 if (isset($key->privateKey
)) {
3470 return $this->computeKeyIdentifier($key->privateKey
, $method);
3472 if (isset($key->currentCert
['tbsCertificate']) ||
isset($key->currentCert
['certificationRequestInfo'])) {
3473 return $this->computeKeyIdentifier($key->currentCert
, $method);
3476 default: // Should be a key object (i.e.: Crypt_RSA).
3477 $key = $key->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW
);
3481 // If in PEM format, convert to binary.
3482 if (preg_match('#^-----BEGIN #', $key)) {
3483 $key = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $key));
3486 // Now we have the key string: compute its sha-1 sum.
3487 if (!class_exists('Crypt_Hash')) {
3488 require_once('Crypt/Hash.php');
3490 $hash = new Crypt_Hash('sha1');
3491 $hash = $hash->hash($key);
3494 $hash = substr($hash, -8);
3495 $hash[0] = chr((ord($hash[0]) & 0x0F) |
0x40);
3502 * Format a public key as appropriate
3507 function _formatSubjectPublicKey()
3509 if (!isset($this->publicKey
) ||
!is_object($this->publicKey
)) {
3513 switch (strtolower(get_class($this->publicKey
))) {
3515 // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
3516 // the former is a good example of how to do fuzzing on the public key
3517 //return new File_ASN1_Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())));
3519 'algorithm' => array('algorithm' => 'rsaEncryption'),
3520 'subjectPublicKey' => $this->publicKey
->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW
)
3528 * Set the domain name's which the cert is to be valid for
3533 function setDomain()
3535 $this->domains
= func_get_args();
3536 $this->removeDNProp('id-at-commonName');
3537 $this->setDNProp('id-at-commonName', $this->domains
[0]);
3541 * Helper function to build domain array
3544 * @param String $domain
3547 function _dnsName($domain)
3549 return array('dNSName' => $domain);
3553 * Get the index of a revoked certificate.
3555 * @param array $rclist
3556 * @param String $serial
3557 * @param Boolean $create optional
3559 * @return Integer or false
3561 function _revokedCertificate(&$rclist, $serial, $create = false)
3563 $serial = new Math_BigInteger($serial);
3565 foreach ($rclist as $i => $rc) {
3566 if (!($serial->compare($rc['userCertificate']))) {
3575 $i = count($rclist);
3576 $rclist[] = array('userCertificate' => $serial,
3577 'revocationDate' => array('generalTime' => @date
('M j H:i:s Y T')));
3582 * Revoke a certificate.
3584 * @param String $serial
3585 * @param String $date optional
3589 function revoke($serial, $date = NULL)
3591 if (isset($this->currentCert
['tbsCertList'])) {
3592 if (is_array($rclist = &$this->_subArray($this->currentCert
, 'tbsCertList/revokedCertificates', true))) {
3593 if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked
3594 if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
3596 if (!empty($date)) {
3597 $rclist[$i]['revocationDate'] = array('generalTime' => $date);
3610 * Unrevoke a certificate.
3612 * @param String $serial
3616 function unrevoke($serial)
3618 if (is_array($rclist = &$this->_subArray($this->currentCert
, 'tbsCertList/revokedCertificates'))) {
3619 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
3621 $rclist = array_values($rclist);
3630 * Get a revoked certificate.
3632 * @param String $serial
3636 function getRevoked($serial)
3638 if (is_array($rclist = $this->_subArray($this->currentCert
, 'tbsCertList/revokedCertificates'))) {
3639 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
3648 * List revoked certificates
3650 * @param array $crl optional
3654 function listRevoked($crl = NULL)
3657 $crl = $this->currentCert
;
3660 if (!isset($crl['tbsCertList'])) {
3666 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
3667 foreach ($rclist as $rc) {
3668 $result[] = $rc['userCertificate']->toString();
3676 * Remove a Revoked Certificate Extension
3678 * @param String $serial
3683 function removeRevokedCertificateExtension($serial, $id)
3685 if (is_array($rclist = &$this->_subArray($this->currentCert
, 'tbsCertList/revokedCertificates'))) {
3686 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
3687 return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
3695 * Get a Revoked Certificate Extension
3697 * Returns the extension if it exists and false if not
3699 * @param String $serial
3701 * @param Array $crl optional
3705 function getRevokedCertificateExtension($serial, $id, $crl = NULL)
3708 $crl = $this->currentCert
;
3711 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
3712 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
3713 return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
3721 * Returns a list of all extensions in use for a given revoked certificate
3723 * @param String $serial
3724 * @param array $crl optional
3728 function getRevokedCertificateExtensions($serial, $crl = NULL)
3731 $crl = $this->currentCert
;
3734 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
3735 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
3736 return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
3744 * Set a Revoked Certificate Extension
3746 * @param String $serial
3748 * @param Mixed $value
3749 * @param Boolean $critical optional
3750 * @param Boolean $replace optional
3754 function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
3756 if (isset($this->currentCert
['tbsCertList'])) {
3757 if (is_array($rclist = &$this->_subArray($this->currentCert
, 'tbsCertList/revokedCertificates', true))) {
3758 if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
3759 return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");