4 * Pure-PHP X.509 Parser
8 * Encode and decode X.509 certificates.
10 * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
11 * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
13 * Note that loading an X.509 certificate and resaving it may invalidate the signature. The reason being that the signature is based on a
14 * portion of the certificate that contains optional parameters with default values. ie. if the parameter isn't there the default value is
15 * used. Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
16 * be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the
17 * the certificate all together unless the certificate is re-signed.
19 * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
20 * of this software and associated documentation files (the "Software"), to deal
21 * in the Software without restriction, including without limitation the rights
22 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 * copies of the Software, and to permit persons to whom the Software is
24 * furnished to do so, subject to the following conditions:
26 * The above copyright notice and this permission notice shall be included in
27 * all copies or substantial portions of the Software.
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * @author Jim Wigginton <terrafrost@php.net>
40 * @copyright 2012 Jim Wigginton
41 * @license http://www.opensource.org/licenses/mit-license.html MIT License
42 * @link http://phpseclib.sourceforge.net
48 if (!class_exists('File_ASN1')) {
49 include_once 'ASN1.php';
53 * Flag to only accept signatures signed by certificate authorities
55 * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
59 define('FILE_X509_VALIDATE_SIGNATURE_BY_CA', 1);
63 * @see File_X509::getDN()
66 * Return internal array representation
68 define('FILE_X509_DN_ARRAY', 0);
72 define('FILE_X509_DN_STRING', 1);
74 * Return ASN.1 name string
76 define('FILE_X509_DN_ASN1', 2);
78 * Return OpenSSL compatible array
80 define('FILE_X509_DN_OPENSSL', 3);
82 * Return canonical ASN.1 RDNs string
84 define('FILE_X509_DN_CANON', 4);
86 * Return name hash for file indexing
88 define('FILE_X509_DN_HASH', 5);
93 * @see File_X509::saveX509()
94 * @see File_X509::saveCSR()
95 * @see File_X509::saveCRL()
100 * ie. a base64-encoded PEM with a header and a footer
102 define('FILE_X509_FORMAT_PEM', 0);
106 define('FILE_X509_FORMAT_DER', 1);
110 * Only works on CSRs. Not currently supported.
112 define('FILE_X509_FORMAT_SPKAC', 2);
116 * Attribute value disposition.
117 * If disposition is >= 0, this is the index of the target value.
119 define('FILE_X509_ATTR_ALL', -1); // All attribute values (array).
120 define('FILE_X509_ATTR_APPEND', -2); // Add a value.
121 define('FILE_X509_ATTR_REPLACE', -3); // Clear first, then add a value.
124 * Pure-PHP X.509 Parser
127 * @author Jim Wigginton <terrafrost@php.net>
133 * ASN.1 syntax for X.509 certificates
141 * ASN.1 syntax for various extensions
145 var $DirectoryString;
150 var $ExtKeyUsageSyntax;
151 var $BasicConstraints;
153 var $CRLDistributionPoints;
154 var $AuthorityKeyIdentifier;
155 var $CertificatePolicies;
156 var $AuthorityInfoAccessSyntax;
158 var $PrivateKeyUsagePeriod;
161 var $NameConstraints;
166 var $netscape_cert_type;
167 var $netscape_comment;
168 var $netscape_ca_policy_url;
171 var $RelativeDistinguishedName;
174 var $IssuingDistributionPoint;
176 var $CertificateIssuer;
177 var $HoldInstructionCode;
178 var $SignedPublicKeyAndChallenge;
182 * ASN.1 syntax for Certificate Signing Requests (RFC2986)
187 var $CertificationRequest;
190 * ASN.1 syntax for Certificate Revocation Lists (RFC5280)
195 var $CertificateList;
222 * Object identifiers for X.509 certificates
226 * @link http://en.wikipedia.org/wiki/Object_identifier
231 * The certificate authorities
239 * The currently loaded certificate
247 * The signature subject
249 * There's no guarantee File_X509 is going to reencode an X.509 cert in the same way it was originally
250 * encoded so we take save the portion of the original cert that the signature would have made for.
255 var $signatureSubject;
258 * Certificate Start Date
266 * Certificate End Date
284 * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
285 * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
290 var $currentKeyIdentifier;
309 * Default Constructor.
316 if (!class_exists('Math_BigInteger')) {
317 include_once 'Math/BigInteger.php';
320 // Explicitly Tagged Module, 1988 Syntax
321 // http://tools.ietf.org/html/rfc5280#appendix-A.1
323 $this->DirectoryString = array(
324 'type' => FILE_ASN1_TYPE_CHOICE,
326 'teletexString' => array('type' => FILE_ASN1_TYPE_TELETEX_STRING),
327 'printableString' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING),
328 'universalString' => array('type' => FILE_ASN1_TYPE_UNIVERSAL_STRING),
329 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING),
330 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING)
334 $this->PKCS9String = array(
335 'type' => FILE_ASN1_TYPE_CHOICE,
337 'ia5String' => array('type' => FILE_ASN1_TYPE_IA5_STRING),
338 'directoryString' => $this->DirectoryString
342 $this->AttributeValue = array('type' => FILE_ASN1_TYPE_ANY);
344 $AttributeType = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
346 $AttributeTypeAndValue = array(
347 'type' => FILE_ASN1_TYPE_SEQUENCE,
349 'type' => $AttributeType,
350 'value'=> $this->AttributeValue
355 In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
356 but they can be useful at times when either there is no unique attribute in the entry or you
357 want to ensure that the entry's DN contains some useful identifying information.
359 - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
361 $this->RelativeDistinguishedName = array(
362 'type' => FILE_ASN1_TYPE_SET,
365 'children' => $AttributeTypeAndValue
368 // http://tools.ietf.org/html/rfc5280#section-4.1.2.4
369 $RDNSequence = array(
370 'type' => FILE_ASN1_TYPE_SEQUENCE,
371 // RDNSequence does not define a min or a max, which means it doesn't have one
374 'children' => $this->RelativeDistinguishedName
378 'type' => FILE_ASN1_TYPE_CHOICE,
380 'rdnSequence' => $RDNSequence
384 // http://tools.ietf.org/html/rfc5280#section-4.1.1.2
385 $AlgorithmIdentifier = array(
386 'type' => FILE_ASN1_TYPE_SEQUENCE,
388 'algorithm' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
389 'parameters' => array(
390 'type' => FILE_ASN1_TYPE_ANY,
397 A certificate using system MUST reject the certificate if it encounters
398 a critical extension it does not recognize; however, a non-critical
399 extension may be ignored if it is not recognized.
401 http://tools.ietf.org/html/rfc5280#section-4.2
404 'type' => FILE_ASN1_TYPE_SEQUENCE,
406 'extnId' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
408 'type' => FILE_ASN1_TYPE_BOOLEAN,
412 'extnValue' => array('type' => FILE_ASN1_TYPE_OCTET_STRING)
416 $this->Extensions = array(
417 'type' => FILE_ASN1_TYPE_SEQUENCE,
419 // technically, it's MAX, but we'll assume anything < 0 is MAX
421 // if 'children' isn't an array then 'min' and 'max' must be defined
422 'children' => $Extension
425 $SubjectPublicKeyInfo = array(
426 'type' => FILE_ASN1_TYPE_SEQUENCE,
428 'algorithm' => $AlgorithmIdentifier,
429 'subjectPublicKey' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
433 $UniqueIdentifier = array('type' => FILE_ASN1_TYPE_BIT_STRING);
436 'type' => FILE_ASN1_TYPE_CHOICE,
438 'utcTime' => array('type' => FILE_ASN1_TYPE_UTC_TIME),
439 'generalTime' => array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME)
443 // http://tools.ietf.org/html/rfc5280#section-4.1.2.5
445 'type' => FILE_ASN1_TYPE_SEQUENCE,
447 'notBefore' => $Time,
452 $CertificateSerialNumber = array('type' => FILE_ASN1_TYPE_INTEGER);
455 'type' => FILE_ASN1_TYPE_INTEGER,
456 'mapping' => array('v1', 'v2', 'v3')
459 // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
460 $TBSCertificate = array(
461 'type' => FILE_ASN1_TYPE_SEQUENCE,
463 // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
464 // reenforce that fact
471 'serialNumber' => $CertificateSerialNumber,
472 'signature' => $AlgorithmIdentifier,
473 'issuer' => $this->Name,
474 'validity' => $Validity,
475 'subject' => $this->Name,
476 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo,
477 // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
478 'issuerUniqueID' => array(
482 ) + $UniqueIdentifier,
483 'subjectUniqueID' => array(
487 ) + $UniqueIdentifier,
488 // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
489 // it's not IMPLICIT, it's EXPLICIT
490 'extensions' => array(
494 ) + $this->Extensions
498 $this->Certificate = array(
499 'type' => FILE_ASN1_TYPE_SEQUENCE,
501 'tbsCertificate' => $TBSCertificate,
502 'signatureAlgorithm' => $AlgorithmIdentifier,
503 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
507 $this->KeyUsage = array(
508 'type' => FILE_ASN1_TYPE_BIT_STRING,
522 $this->BasicConstraints = array(
523 'type' => FILE_ASN1_TYPE_SEQUENCE,
526 'type' => FILE_ASN1_TYPE_BOOLEAN,
530 'pathLenConstraint' => array(
531 'type' => FILE_ASN1_TYPE_INTEGER,
537 $this->KeyIdentifier = array('type' => FILE_ASN1_TYPE_OCTET_STRING);
539 $OrganizationalUnitNames = array(
540 'type' => FILE_ASN1_TYPE_SEQUENCE,
542 'max' => 4, // ub-organizational-units
543 'children' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
546 $PersonalName = array(
547 'type' => FILE_ASN1_TYPE_SET,
550 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
555 'given-name' => array(
556 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
562 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
567 'generation-qualifier' => array(
568 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
576 $NumericUserIdentifier = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING);
578 $OrganizationName = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING);
580 $PrivateDomainName = array(
581 'type' => FILE_ASN1_TYPE_CHOICE,
583 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING),
584 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
588 $TerminalIdentifier = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING);
590 $NetworkAddress = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING);
592 $AdministrationDomainName = array(
593 'type' => FILE_ASN1_TYPE_CHOICE,
594 // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or
595 // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC
596 'class' => FILE_ASN1_CLASS_APPLICATION,
599 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING),
600 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
604 $CountryName = array(
605 'type' => FILE_ASN1_TYPE_CHOICE,
606 // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or
607 // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC
608 'class' => FILE_ASN1_CLASS_APPLICATION,
611 'x121-dcc-code' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING),
612 'iso-3166-alpha2-code' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
616 $AnotherName = array(
617 'type' => FILE_ASN1_TYPE_SEQUENCE,
619 'type-id' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
621 'type' => FILE_ASN1_TYPE_ANY,
629 $ExtensionAttribute = array(
630 'type' => FILE_ASN1_TYPE_SEQUENCE,
632 'extension-attribute-type' => array(
633 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
638 'extension-attribute-value' => array(
639 'type' => FILE_ASN1_TYPE_ANY,
647 $ExtensionAttributes = array(
648 'type' => FILE_ASN1_TYPE_SET,
650 'max' => 256, // ub-extension-attributes
651 'children' => $ExtensionAttribute
654 $BuiltInDomainDefinedAttribute = array(
655 'type' => FILE_ASN1_TYPE_SEQUENCE,
657 'type' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING),
658 'value' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
662 $BuiltInDomainDefinedAttributes = array(
663 'type' => FILE_ASN1_TYPE_SEQUENCE,
665 'max' => 4, // ub-domain-defined-attributes
666 'children' => $BuiltInDomainDefinedAttribute
669 $BuiltInStandardAttributes = array(
670 'type' => FILE_ASN1_TYPE_SEQUENCE,
672 'country-name' => array('optional' => true) + $CountryName,
673 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName,
674 'network-address' => array(
679 'terminal-identifier' => array(
683 ) + $TerminalIdentifier,
684 'private-domain-name' => array(
688 ) + $PrivateDomainName,
689 'organization-name' => array(
693 ) + $OrganizationName,
694 'numeric-user-identifier' => array(
698 ) + $NumericUserIdentifier,
699 'personal-name' => array(
704 'organizational-unit-names' => array(
708 ) + $OrganizationalUnitNames
713 'type' => FILE_ASN1_TYPE_SEQUENCE,
715 'built-in-standard-attributes' => $BuiltInStandardAttributes,
716 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes,
717 'extension-attributes' => array('optional' => true) + $ExtensionAttributes
721 $EDIPartyName = array(
722 'type' => FILE_ASN1_TYPE_SEQUENCE,
724 'nameAssigner' => array(
728 ) + $this->DirectoryString,
729 // partyName is technically required but File_ASN1 doesn't currently support non-optional constants and
730 // setting it to optional gets the job done in any event.
731 'partyName' => array(
735 ) + $this->DirectoryString
739 $GeneralName = array(
740 'type' => FILE_ASN1_TYPE_CHOICE,
742 'otherName' => array(
747 'rfc822Name' => array(
748 'type' => FILE_ASN1_TYPE_IA5_STRING,
754 'type' => FILE_ASN1_TYPE_IA5_STRING,
759 'x400Address' => array(
764 'directoryName' => array(
769 'ediPartyName' => array(
774 'uniformResourceIdentifier' => array(
775 'type' => FILE_ASN1_TYPE_IA5_STRING,
780 'iPAddress' => array(
781 'type' => FILE_ASN1_TYPE_OCTET_STRING,
786 'registeredID' => array(
787 'type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER,
795 $GeneralNames = array(
796 'type' => FILE_ASN1_TYPE_SEQUENCE,
799 'children' => $GeneralName
802 $this->IssuerAltName = $GeneralNames;
804 $ReasonFlags = array(
805 'type' => FILE_ASN1_TYPE_BIT_STRING,
810 'affiliationChanged',
812 'cessationOfOperation',
814 'privilegeWithdrawn',
819 $DistributionPointName = array(
820 'type' => FILE_ASN1_TYPE_CHOICE,
827 'nameRelativeToCRLIssuer' => array(
831 ) + $this->RelativeDistinguishedName
835 $DistributionPoint = array(
836 'type' => FILE_ASN1_TYPE_SEQUENCE,
838 'distributionPoint' => array(
842 ) + $DistributionPointName,
848 'cRLIssuer' => array(
856 $this->CRLDistributionPoints = array(
857 'type' => FILE_ASN1_TYPE_SEQUENCE,
860 'children' => $DistributionPoint
863 $this->AuthorityKeyIdentifier = array(
864 'type' => FILE_ASN1_TYPE_SEQUENCE,
866 'keyIdentifier' => array(
870 ) + $this->KeyIdentifier,
871 'authorityCertIssuer' => array(
876 'authorityCertSerialNumber' => array(
880 ) + $CertificateSerialNumber
884 $PolicyQualifierId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
886 $PolicyQualifierInfo = array(
887 'type' => FILE_ASN1_TYPE_SEQUENCE,
889 'policyQualifierId' => $PolicyQualifierId,
890 'qualifier' => array('type' => FILE_ASN1_TYPE_ANY)
894 $CertPolicyId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
896 $PolicyInformation = array(
897 'type' => FILE_ASN1_TYPE_SEQUENCE,
899 'policyIdentifier' => $CertPolicyId,
900 'policyQualifiers' => array(
901 'type' => FILE_ASN1_TYPE_SEQUENCE,
905 'children' => $PolicyQualifierInfo
910 $this->CertificatePolicies = array(
911 'type' => FILE_ASN1_TYPE_SEQUENCE,
914 'children' => $PolicyInformation
917 $this->PolicyMappings = array(
918 'type' => FILE_ASN1_TYPE_SEQUENCE,
922 'type' => FILE_ASN1_TYPE_SEQUENCE,
924 'issuerDomainPolicy' => $CertPolicyId,
925 'subjectDomainPolicy' => $CertPolicyId
930 $KeyPurposeId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
932 $this->ExtKeyUsageSyntax = array(
933 'type' => FILE_ASN1_TYPE_SEQUENCE,
936 'children' => $KeyPurposeId
939 $AccessDescription = array(
940 'type' => FILE_ASN1_TYPE_SEQUENCE,
942 'accessMethod' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
943 'accessLocation' => $GeneralName
947 $this->AuthorityInfoAccessSyntax = array(
948 'type' => FILE_ASN1_TYPE_SEQUENCE,
951 'children' => $AccessDescription
954 $this->SubjectAltName = $GeneralNames;
956 $this->PrivateKeyUsagePeriod = array(
957 'type' => FILE_ASN1_TYPE_SEQUENCE,
959 'notBefore' => array(
963 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME),
968 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME)
972 $BaseDistance = array('type' => FILE_ASN1_TYPE_INTEGER);
974 $GeneralSubtree = array(
975 'type' => FILE_ASN1_TYPE_SEQUENCE,
977 'base' => $GeneralName,
982 'default' => new Math_BigInteger(0)
992 $GeneralSubtrees = array(
993 'type' => FILE_ASN1_TYPE_SEQUENCE,
996 'children' => $GeneralSubtree
999 $this->NameConstraints = array(
1000 'type' => FILE_ASN1_TYPE_SEQUENCE,
1001 'children' => array(
1002 'permittedSubtrees' => array(
1006 ) + $GeneralSubtrees,
1007 'excludedSubtrees' => array(
1011 ) + $GeneralSubtrees
1015 $this->CPSuri = array('type' => FILE_ASN1_TYPE_IA5_STRING);
1017 $DisplayText = array(
1018 'type' => FILE_ASN1_TYPE_CHOICE,
1019 'children' => array(
1020 'ia5String' => array('type' => FILE_ASN1_TYPE_IA5_STRING),
1021 'visibleString' => array('type' => FILE_ASN1_TYPE_VISIBLE_STRING),
1022 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING),
1023 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING)
1027 $NoticeReference = array(
1028 'type' => FILE_ASN1_TYPE_SEQUENCE,
1029 'children' => array(
1030 'organization' => $DisplayText,
1031 'noticeNumbers' => array(
1032 'type' => FILE_ASN1_TYPE_SEQUENCE,
1035 'children' => array('type' => FILE_ASN1_TYPE_INTEGER)
1040 $this->UserNotice = array(
1041 'type' => FILE_ASN1_TYPE_SEQUENCE,
1042 'children' => array(
1043 'noticeRef' => array(
1046 ) + $NoticeReference,
1047 'explicitText' => array(
1054 // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
1055 $this->netscape_cert_type = array(
1056 'type' => FILE_ASN1_TYPE_BIT_STRING,
1069 $this->netscape_comment = array('type' => FILE_ASN1_TYPE_IA5_STRING);
1070 $this->netscape_ca_policy_url = array('type' => FILE_ASN1_TYPE_IA5_STRING);
1072 // attribute is used in RFC2986 but we're using the RFC5280 definition
1075 'type' => FILE_ASN1_TYPE_SEQUENCE,
1076 'children' => array(
1077 'type' => $AttributeType,
1079 'type' => FILE_ASN1_TYPE_SET,
1082 'children' => $this->AttributeValue
1087 // adapted from <http://tools.ietf.org/html/rfc2986>
1089 $Attributes = array(
1090 'type' => FILE_ASN1_TYPE_SET,
1093 'children' => $Attribute
1096 $CertificationRequestInfo = array(
1097 'type' => FILE_ASN1_TYPE_SEQUENCE,
1098 'children' => array(
1100 'type' => FILE_ASN1_TYPE_INTEGER,
1101 'mapping' => array('v1')
1103 'subject' => $this->Name,
1104 'subjectPKInfo' => $SubjectPublicKeyInfo,
1105 'attributes' => array(
1113 $this->CertificationRequest = array(
1114 'type' => FILE_ASN1_TYPE_SEQUENCE,
1115 'children' => array(
1116 'certificationRequestInfo' => $CertificationRequestInfo,
1117 'signatureAlgorithm' => $AlgorithmIdentifier,
1118 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
1122 $RevokedCertificate = array(
1123 'type' => FILE_ASN1_TYPE_SEQUENCE,
1124 'children' => array(
1125 'userCertificate' => $CertificateSerialNumber,
1126 'revocationDate' => $Time,
1127 'crlEntryExtensions' => array(
1129 ) + $this->Extensions
1133 $TBSCertList = array(
1134 'type' => FILE_ASN1_TYPE_SEQUENCE,
1135 'children' => array(
1140 'signature' => $AlgorithmIdentifier,
1141 'issuer' => $this->Name,
1142 'thisUpdate' => $Time,
1143 'nextUpdate' => array(
1146 'revokedCertificates' => array(
1147 'type' => FILE_ASN1_TYPE_SEQUENCE,
1151 'children' => $RevokedCertificate
1153 'crlExtensions' => array(
1157 ) + $this->Extensions
1161 $this->CertificateList = array(
1162 'type' => FILE_ASN1_TYPE_SEQUENCE,
1163 'children' => array(
1164 'tbsCertList' => $TBSCertList,
1165 'signatureAlgorithm' => $AlgorithmIdentifier,
1166 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
1170 $this->CRLNumber = array('type' => FILE_ASN1_TYPE_INTEGER);
1172 $this->CRLReason = array('type' => FILE_ASN1_TYPE_ENUMERATED,
1177 'affiliationChanged',
1179 'cessationOfOperation',
1181 // Value 7 is not used.
1182 8 => 'removeFromCRL',
1183 'privilegeWithdrawn',
1188 $this->IssuingDistributionPoint = array('type' => FILE_ASN1_TYPE_SEQUENCE,
1189 'children' => array(
1190 'distributionPoint' => array(
1194 ) + $DistributionPointName,
1195 'onlyContainsUserCerts' => array(
1196 'type' => FILE_ASN1_TYPE_BOOLEAN,
1202 'onlyContainsCACerts' => array(
1203 'type' => FILE_ASN1_TYPE_BOOLEAN,
1209 'onlySomeReasons' => array(
1214 'indirectCRL' => array(
1215 'type' => FILE_ASN1_TYPE_BOOLEAN,
1221 'onlyContainsAttributeCerts' => array(
1222 'type' => FILE_ASN1_TYPE_BOOLEAN,
1231 $this->InvalidityDate = array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME);
1233 $this->CertificateIssuer = $GeneralNames;
1235 $this->HoldInstructionCode = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
1237 $PublicKeyAndChallenge = array(
1238 'type' => FILE_ASN1_TYPE_SEQUENCE,
1239 'children' => array(
1240 'spki' => $SubjectPublicKeyInfo,
1241 'challenge' => array('type' => FILE_ASN1_TYPE_IA5_STRING)
1245 $this->SignedPublicKeyAndChallenge = array(
1246 'type' => FILE_ASN1_TYPE_SEQUENCE,
1247 'children' => array(
1248 'publicKeyAndChallenge' => $PublicKeyAndChallenge,
1249 'signatureAlgorithm' => $AlgorithmIdentifier,
1250 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
1254 // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
1255 $this->oids = array(
1256 '1.3.6.1.5.5.7' => 'id-pkix',
1257 '1.3.6.1.5.5.7.1' => 'id-pe',
1258 '1.3.6.1.5.5.7.2' => 'id-qt',
1259 '1.3.6.1.5.5.7.3' => 'id-kp',
1260 '1.3.6.1.5.5.7.48' => 'id-ad',
1261 '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
1262 '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
1263 '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
1264 '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
1265 '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
1266 '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
1268 '2.5.4.41' => 'id-at-name',
1269 '2.5.4.4' => 'id-at-surname',
1270 '2.5.4.42' => 'id-at-givenName',
1271 '2.5.4.43' => 'id-at-initials',
1272 '2.5.4.44' => 'id-at-generationQualifier',
1273 '2.5.4.3' => 'id-at-commonName',
1274 '2.5.4.7' => 'id-at-localityName',
1275 '2.5.4.8' => 'id-at-stateOrProvinceName',
1276 '2.5.4.10' => 'id-at-organizationName',
1277 '2.5.4.11' => 'id-at-organizationalUnitName',
1278 '2.5.4.12' => 'id-at-title',
1279 '2.5.4.13' => 'id-at-description',
1280 '2.5.4.46' => 'id-at-dnQualifier',
1281 '2.5.4.6' => 'id-at-countryName',
1282 '2.5.4.5' => 'id-at-serialNumber',
1283 '2.5.4.65' => 'id-at-pseudonym',
1284 '2.5.4.17' => 'id-at-postalCode',
1285 '2.5.4.9' => 'id-at-streetAddress',
1286 '2.5.4.45' => 'id-at-uniqueIdentifier',
1287 '2.5.4.72' => 'id-at-role',
1289 '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
1290 '1.2.840.113549.1.9' => 'pkcs-9',
1291 '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress',
1292 '2.5.29' => 'id-ce',
1293 '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
1294 '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
1295 '2.5.29.15' => 'id-ce-keyUsage',
1296 '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
1297 '2.5.29.32' => 'id-ce-certificatePolicies',
1298 '2.5.29.32.0' => 'anyPolicy',
1300 '2.5.29.33' => 'id-ce-policyMappings',
1301 '2.5.29.17' => 'id-ce-subjectAltName',
1302 '2.5.29.18' => 'id-ce-issuerAltName',
1303 '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
1304 '2.5.29.19' => 'id-ce-basicConstraints',
1305 '2.5.29.30' => 'id-ce-nameConstraints',
1306 '2.5.29.36' => 'id-ce-policyConstraints',
1307 '2.5.29.31' => 'id-ce-cRLDistributionPoints',
1308 '2.5.29.37' => 'id-ce-extKeyUsage',
1309 '2.5.29.37.0' => 'anyExtendedKeyUsage',
1310 '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
1311 '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
1312 '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
1313 '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
1314 '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
1315 '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
1316 '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
1317 '2.5.29.46' => 'id-ce-freshestCRL',
1318 '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
1319 '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
1320 '2.5.29.20' => 'id-ce-cRLNumber',
1321 '2.5.29.28' => 'id-ce-issuingDistributionPoint',
1322 '2.5.29.27' => 'id-ce-deltaCRLIndicator',
1323 '2.5.29.21' => 'id-ce-cRLReasons',
1324 '2.5.29.29' => 'id-ce-certificateIssuer',
1325 '2.5.29.23' => 'id-ce-holdInstructionCode',
1326 '1.2.840.10040.2' => 'holdInstruction',
1327 '1.2.840.10040.2.1' => 'id-holdinstruction-none',
1328 '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
1329 '1.2.840.10040.2.3' => 'id-holdinstruction-reject',
1330 '2.5.29.24' => 'id-ce-invalidityDate',
1332 '1.2.840.113549.2.2' => 'md2',
1333 '1.2.840.113549.2.5' => 'md5',
1334 '1.3.14.3.2.26' => 'id-sha1',
1335 '1.2.840.10040.4.1' => 'id-dsa',
1336 '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
1337 '1.2.840.113549.1.1' => 'pkcs-1',
1338 '1.2.840.113549.1.1.1' => 'rsaEncryption',
1339 '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
1340 '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
1341 '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
1342 '1.2.840.10046.2.1' => 'dhpublicnumber',
1343 '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
1344 '1.2.840.10045' => 'ansi-X9-62',
1345 '1.2.840.10045.4' => 'id-ecSigType',
1346 '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
1347 '1.2.840.10045.1' => 'id-fieldType',
1348 '1.2.840.10045.1.1' => 'prime-field',
1349 '1.2.840.10045.1.2' => 'characteristic-two-field',
1350 '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
1351 '1.2.840.10045.1.2.3.1' => 'gnBasis',
1352 '1.2.840.10045.1.2.3.2' => 'tpBasis',
1353 '1.2.840.10045.1.2.3.3' => 'ppBasis',
1354 '1.2.840.10045.2' => 'id-publicKeyType',
1355 '1.2.840.10045.2.1' => 'id-ecPublicKey',
1356 '1.2.840.10045.3' => 'ellipticCurve',
1357 '1.2.840.10045.3.0' => 'c-TwoCurve',
1358 '1.2.840.10045.3.0.1' => 'c2pnb163v1',
1359 '1.2.840.10045.3.0.2' => 'c2pnb163v2',
1360 '1.2.840.10045.3.0.3' => 'c2pnb163v3',
1361 '1.2.840.10045.3.0.4' => 'c2pnb176w1',
1362 '1.2.840.10045.3.0.5' => 'c2pnb191v1',
1363 '1.2.840.10045.3.0.6' => 'c2pnb191v2',
1364 '1.2.840.10045.3.0.7' => 'c2pnb191v3',
1365 '1.2.840.10045.3.0.8' => 'c2pnb191v4',
1366 '1.2.840.10045.3.0.9' => 'c2pnb191v5',
1367 '1.2.840.10045.3.0.10' => 'c2pnb208w1',
1368 '1.2.840.10045.3.0.11' => 'c2pnb239v1',
1369 '1.2.840.10045.3.0.12' => 'c2pnb239v2',
1370 '1.2.840.10045.3.0.13' => 'c2pnb239v3',
1371 '1.2.840.10045.3.0.14' => 'c2pnb239v4',
1372 '1.2.840.10045.3.0.15' => 'c2pnb239v5',
1373 '1.2.840.10045.3.0.16' => 'c2pnb272w1',
1374 '1.2.840.10045.3.0.17' => 'c2pnb304w1',
1375 '1.2.840.10045.3.0.18' => 'c2pnb359v1',
1376 '1.2.840.10045.3.0.19' => 'c2pnb368w1',
1377 '1.2.840.10045.3.0.20' => 'c2pnb431r1',
1378 '1.2.840.10045.3.1' => 'primeCurve',
1379 '1.2.840.10045.3.1.1' => 'prime192v1',
1380 '1.2.840.10045.3.1.2' => 'prime192v2',
1381 '1.2.840.10045.3.1.3' => 'prime192v3',
1382 '1.2.840.10045.3.1.4' => 'prime239v1',
1383 '1.2.840.10045.3.1.5' => 'prime239v2',
1384 '1.2.840.10045.3.1.6' => 'prime239v3',
1385 '1.2.840.10045.3.1.7' => 'prime256v1',
1386 '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
1387 '1.2.840.113549.1.1.9' => 'id-pSpecified',
1388 '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
1389 '1.2.840.113549.1.1.8' => 'id-mgf1',
1390 '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
1391 '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
1392 '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
1393 '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
1394 '2.16.840.1.101.3.4.2.4' => 'id-sha224',
1395 '2.16.840.1.101.3.4.2.1' => 'id-sha256',
1396 '2.16.840.1.101.3.4.2.2' => 'id-sha384',
1397 '2.16.840.1.101.3.4.2.3' => 'id-sha512',
1398 '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
1399 '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
1400 '1.2.643.2.2.20' => 'id-GostR3410-2001',
1401 '1.2.643.2.2.19' => 'id-GostR3410-94',
1402 // Netscape Object Identifiers from "Netscape Certificate Extensions"
1403 '2.16.840.1.113730' => 'netscape',
1404 '2.16.840.1.113730.1' => 'netscape-cert-extension',
1405 '2.16.840.1.113730.1.1' => 'netscape-cert-type',
1406 '2.16.840.1.113730.1.13' => 'netscape-comment',
1407 '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
1408 // the following are X.509 extensions not supported by phpseclib
1409 '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
1410 '1.2.840.113533.7.65.0' => 'entrustVersInfo',
1411 '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
1412 // for Certificate Signing Requests
1413 // see http://tools.ietf.org/html/rfc2985
1414 '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name
1415 '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations
1416 '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request
1421 * Load X.509 certificate
1423 * Returns an associative array describing the X.509 cert or a false if the cert failed to load
1425 * @param String $cert
1429 function loadX509($cert)
1431 if (is_array($cert) && isset($cert['tbsCertificate'])) {
1432 unset($this->currentCert);
1433 unset($this->currentKeyIdentifier);
1434 $this->dn = $cert['tbsCertificate']['subject'];
1435 if (!isset($this->dn)) {
1438 $this->currentCert = $cert;
1440 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
1441 $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
1443 unset($this->signatureSubject);
1448 $asn1 = new File_ASN1();
1450 $cert = $this->_extractBER($cert);
1452 if ($cert === false) {
1453 $this->currentCert = false;
1457 $asn1->loadOIDs($this->oids);
1458 $decoded = $asn1->decodeBER($cert);
1460 if (!empty($decoded)) {
1461 $x509 = $asn1->asn1map($decoded[0], $this->Certificate);
1463 if (!isset($x509) || $x509 === false) {
1464 $this->currentCert = false;
1468 $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
1470 $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
1472 $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
1473 $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
1475 $this->currentCert = $x509;
1476 $this->dn = $x509['tbsCertificate']['subject'];
1478 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
1479 $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
1485 * Save X.509 certificate
1487 * @param Array $cert
1488 * @param Integer $format optional
1492 function saveX509($cert, $format = FILE_X509_FORMAT_PEM)
1494 if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
1499 // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
1500 case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
1501 case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
1504 switch ($algorithm) {
1505 case 'rsaEncryption':
1506 $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
1507 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
1511 $asn1 = new File_ASN1();
1512 $asn1->loadOIDs($this->oids);
1515 $type_utf8_string = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
1516 $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
1517 $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
1518 $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
1519 $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string;
1520 $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string;
1521 $filters['signatureAlgorithm']['parameters'] = $type_utf8_string;
1522 $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
1523 //$filters['policyQualifiers']['qualifier'] = $type_utf8_string;
1524 $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
1525 $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;
1527 /* in the case of policyQualifiers/qualifier, the type has to be FILE_ASN1_TYPE_IA5_STRING.
1528 FILE_ASN1_TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
1531 $filters['policyQualifiers']['qualifier']
1532 = array('type' => FILE_ASN1_TYPE_IA5_STRING);
1534 $asn1->loadFilters($filters);
1536 $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1);
1538 $cert = $asn1->encodeDER($cert, $this->Certificate);
1541 case FILE_X509_FORMAT_DER:
1543 // case FILE_X509_FORMAT_PEM:
1545 return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----';
1550 * Map extension values from octet string to extension-specific internal
1553 * @param Array ref $root
1554 * @param String $path
1555 * @param Object $asn1
1558 function _mapInExtensions(&$root, $path, $asn1)
1560 $extensions = &$this->_subArray($root, $path);
1562 if (is_array($extensions)) {
1563 for ($i = 0; $i < count($extensions); $i++) {
1564 $id = $extensions[$i]['extnId'];
1565 $value = &$extensions[$i]['extnValue'];
1566 $value = base64_decode($value);
1567 $decoded = $asn1->decodeBER($value);
1568 /* [extnValue] contains the DER encoding of an ASN.1 value
1569 corresponding to the extension type identified by extnID */
1570 $map = $this->_getMapping($id);
1571 if (!is_bool($map)) {
1572 $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
1573 $value = $mapped === false ? $decoded[0] : $mapped;
1575 if ($id == 'id-ce-certificatePolicies') {
1576 for ($j = 0; $j < count($value); $j++) {
1577 if (!isset($value[$j]['policyQualifiers'])) {
1580 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
1581 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1582 $map = $this->_getMapping($subid);
1583 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1584 if ($map !== false) {
1585 $decoded = $asn1->decodeBER($subvalue);
1586 $mapped = $asn1->asn1map($decoded[0], $map);
1587 $subvalue = $mapped === false ? $decoded[0] : $mapped;
1593 $value = base64_encode($value);
1600 * Map extension values from extension-specific internal format to
1603 * @param Array ref $root
1604 * @param String $path
1605 * @param Object $asn1
1608 function _mapOutExtensions(&$root, $path, $asn1)
1610 $extensions = &$this->_subArray($root, $path);
1612 if (is_array($extensions)) {
1613 $size = count($extensions);
1614 for ($i = 0; $i < $size; $i++) {
1615 $id = $extensions[$i]['extnId'];
1616 $value = &$extensions[$i]['extnValue'];
1619 case 'id-ce-certificatePolicies':
1620 for ($j = 0; $j < count($value); $j++) {
1621 if (!isset($value[$j]['policyQualifiers'])) {
1624 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
1625 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1626 $map = $this->_getMapping($subid);
1627 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1628 if ($map !== false) {
1629 // by default File_ASN1 will try to render qualifier as a FILE_ASN1_TYPE_IA5_STRING since it's
1630 // actual type is FILE_ASN1_TYPE_ANY
1631 $subvalue = new File_ASN1_Element($asn1->encodeDER($subvalue, $map));
1636 case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
1637 if (isset($value['authorityCertSerialNumber'])) {
1638 if ($value['authorityCertSerialNumber']->toBytes() == '') {
1639 $temp = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
1640 $value['authorityCertSerialNumber'] = new File_ASN1_Element($temp);
1645 /* [extnValue] contains the DER encoding of an ASN.1 value
1646 corresponding to the extension type identified by extnID */
1647 $map = $this->_getMapping($id);
1648 if (is_bool($map)) {
1650 user_error($id . ' is not a currently supported extension');
1651 unset($extensions[$i]);
1654 $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
1655 $value = base64_encode($temp);
1662 * Map attribute values from ANY type to attribute-specific internal
1665 * @param Array ref $root
1666 * @param String $path
1667 * @param Object $asn1
1670 function _mapInAttributes(&$root, $path, $asn1)
1672 $attributes = &$this->_subArray($root, $path);
1674 if (is_array($attributes)) {
1675 for ($i = 0; $i < count($attributes); $i++) {
1676 $id = $attributes[$i]['type'];
1677 /* $value contains the DER encoding of an ASN.1 value
1678 corresponding to the attribute type identified by type */
1679 $map = $this->_getMapping($id);
1680 if (is_array($attributes[$i]['value'])) {
1681 $values = &$attributes[$i]['value'];
1682 for ($j = 0; $j < count($values); $j++) {
1683 $value = $asn1->encodeDER($values[$j], $this->AttributeValue);
1684 $decoded = $asn1->decodeBER($value);
1685 if (!is_bool($map)) {
1686 $mapped = $asn1->asn1map($decoded[0], $map);
1687 if ($mapped !== false) {
1688 $values[$j] = $mapped;
1690 if ($id == 'pkcs-9-at-extensionRequest') {
1691 $this->_mapInExtensions($values, $j, $asn1);
1694 $values[$j] = base64_encode($value);
1703 * Map attribute values from attribute-specific internal format to
1706 * @param Array ref $root
1707 * @param String $path
1708 * @param Object $asn1
1711 function _mapOutAttributes(&$root, $path, $asn1)
1713 $attributes = &$this->_subArray($root, $path);
1715 if (is_array($attributes)) {
1716 $size = count($attributes);
1717 for ($i = 0; $i < $size; $i++) {
1718 /* [value] contains the DER encoding of an ASN.1 value
1719 corresponding to the attribute type identified by type */
1720 $id = $attributes[$i]['type'];
1721 $map = $this->_getMapping($id);
1722 if ($map === false) {
1723 user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
1724 unset($attributes[$i]);
1725 } elseif (is_array($attributes[$i]['value'])) {
1726 $values = &$attributes[$i]['value'];
1727 for ($j = 0; $j < count($values); $j++) {
1729 case 'pkcs-9-at-extensionRequest':
1730 $this->_mapOutExtensions($values, $j, $asn1);
1734 if (!is_bool($map)) {
1735 $temp = $asn1->encodeDER($values[$j], $map);
1736 $decoded = $asn1->decodeBER($temp);
1737 $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue);
1746 * Associate an extension ID to an extension mapping
1748 * @param String $extnId
1752 function _getMapping($extnId)
1754 if (!is_string($extnId)) { // eg. if it's a File_ASN1_Element object
1759 case 'id-ce-keyUsage':
1760 return $this->KeyUsage;
1761 case 'id-ce-basicConstraints':
1762 return $this->BasicConstraints;
1763 case 'id-ce-subjectKeyIdentifier':
1764 return $this->KeyIdentifier;
1765 case 'id-ce-cRLDistributionPoints':
1766 return $this->CRLDistributionPoints;
1767 case 'id-ce-authorityKeyIdentifier':
1768 return $this->AuthorityKeyIdentifier;
1769 case 'id-ce-certificatePolicies':
1770 return $this->CertificatePolicies;
1771 case 'id-ce-extKeyUsage':
1772 return $this->ExtKeyUsageSyntax;
1773 case 'id-pe-authorityInfoAccess':
1774 return $this->AuthorityInfoAccessSyntax;
1775 case 'id-ce-subjectAltName':
1776 return $this->SubjectAltName;
1777 case 'id-ce-privateKeyUsagePeriod':
1778 return $this->PrivateKeyUsagePeriod;
1779 case 'id-ce-issuerAltName':
1780 return $this->IssuerAltName;
1781 case 'id-ce-policyMappings':
1782 return $this->PolicyMappings;
1783 case 'id-ce-nameConstraints':
1784 return $this->NameConstraints;
1786 case 'netscape-cert-type':
1787 return $this->netscape_cert_type;
1788 case 'netscape-comment':
1789 return $this->netscape_comment;
1790 case 'netscape-ca-policy-url':
1791 return $this->netscape_ca_policy_url;
1793 // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
1794 // back around to asn1map() and we don't want it decoded again.
1796 // return $this->CPSuri;
1797 case 'id-qt-unotice':
1798 return $this->UserNotice;
1800 // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
1801 case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
1802 case 'entrustVersInfo':
1803 // http://support.microsoft.com/kb/287547
1804 case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
1805 case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
1806 // "SET Secure Electronic Transaction Specification"
1807 // http://www.maithean.com/docs/set_bk3.pdf
1808 case '2.23.42.7.0': // id-set-hashedRootKey
1812 case 'pkcs-9-at-unstructuredName':
1813 return $this->PKCS9String;
1814 case 'pkcs-9-at-challengePassword':
1815 return $this->DirectoryString;
1816 case 'pkcs-9-at-extensionRequest':
1817 return $this->Extensions;
1820 case 'id-ce-cRLNumber':
1821 return $this->CRLNumber;
1822 case 'id-ce-deltaCRLIndicator':
1823 return $this->CRLNumber;
1824 case 'id-ce-issuingDistributionPoint':
1825 return $this->IssuingDistributionPoint;
1826 case 'id-ce-freshestCRL':
1827 return $this->CRLDistributionPoints;
1828 case 'id-ce-cRLReasons':
1829 return $this->CRLReason;
1830 case 'id-ce-invalidityDate':
1831 return $this->InvalidityDate;
1832 case 'id-ce-certificateIssuer':
1833 return $this->CertificateIssuer;
1834 case 'id-ce-holdInstructionCode':
1835 return $this->HoldInstructionCode;
1842 * Load an X.509 certificate as a certificate authority
1844 * @param String $cert
1848 function loadCA($cert)
1851 $oldcert = $this->currentCert;
1852 $oldsigsubj = $this->signatureSubject;
1853 $oldkeyid = $this->currentKeyIdentifier;
1855 $cert = $this->loadX509($cert);
1858 $this->currentCert = $oldcert;
1859 $this->signatureSubject = $oldsigsubj;
1860 $this->currentKeyIdentifier = $oldkeyid;
1865 /* From RFC5280 "PKIX Certificate and CRL Profile":
1867 If the keyUsage extension is present, then the subject public key
1868 MUST NOT be used to verify signatures on certificates or CRLs unless
1869 the corresponding keyCertSign or cRLSign bit is set. */
1870 //$keyUsage = $this->getExtension('id-ce-keyUsage');
1871 //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
1875 /* From RFC5280 "PKIX Certificate and CRL Profile":
1877 The cA boolean indicates whether the certified public key may be used
1878 to verify certificate signatures. If the cA boolean is not asserted,
1879 then the keyCertSign bit in the key usage extension MUST NOT be
1880 asserted. If the basic constraints extension is not present in a
1881 version 3 certificate, or the extension is present but the cA boolean
1882 is not asserted, then the certified public key MUST NOT be used to
1883 verify certificate signatures. */
1884 //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
1885 //if (!$basicConstraints || !$basicConstraints['cA']) {
1889 $this->CAs[] = $cert;
1892 $this->currentCert = $oldcert;
1893 $this->signatureSubject = $oldsigsubj;
1899 * Validate an X.509 certificate against a URL
1901 * From RFC2818 "HTTP over TLS":
1903 * Matching is performed using the matching rules specified by
1904 * [RFC2459]. If more than one identity of a given type is present in
1905 * the certificate (e.g., more than one dNSName name, a match in any one
1906 * of the set is considered acceptable.) Names may contain the wildcard
1907 * character * which is considered to match any single domain name
1908 * component or component fragment. E.g., *.a.com matches foo.a.com but
1909 * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
1911 * @param String $url
1915 function validateURL($url)
1917 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
1921 $components = parse_url($url);
1922 if (!isset($components['host'])) {
1926 if ($names = $this->getExtension('id-ce-subjectAltName')) {
1927 foreach ($names as $key => $value) {
1928 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
1931 /* From RFC2818 "HTTP over TLS":
1933 If a subjectAltName extension of type dNSName is present, that MUST
1934 be used as the identity. Otherwise, the (most specific) Common Name
1935 field in the Subject field of the certificate MUST be used. Although
1936 the use of the Common Name is existing practice, it is deprecated and
1937 Certification Authorities are encouraged to use the dNSName instead. */
1938 if (preg_match('#^' . $value . '$#', $components['host'])) {
1943 /* From RFC2818 "HTTP over TLS":
1945 In some cases, the URI is specified as an IP address rather than a
1946 hostname. In this case, the iPAddress subjectAltName must be present
1947 in the certificate and must exactly match the IP in the URI. */
1948 if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
1956 if ($value = $this->getDNProp('id-at-commonName')) {
1957 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
1958 return preg_match('#^' . $value . '$#', $components['host']);
1967 * If $date isn't defined it is assumed to be the current date.
1969 * @param Integer $date optional
1972 function validateDate($date = null)
1974 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
1978 if (!isset($date)) {
1982 $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
1983 $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];
1985 $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
1986 $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
1989 case $date < @strtotime($notBefore):
1990 case $date > @strtotime($notAfter):
1998 * Validate a signature
2000 * Works on X.509 certs, CSR's and CRL's.
2001 * Returns true if the signature is verified, false if it is not correct or null on error
2003 * By default returns false for self-signed certs. Call validateSignature(false) to make this support
2006 * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
2008 * @param Boolean $caonly optional
2012 function validateSignature($caonly = true)
2014 if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
2019 "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
2020 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
2022 implement pathLenConstraint in the id-ce-basicConstraints extension */
2025 case isset($this->currentCert['tbsCertificate']):
2027 if ($this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']) {
2028 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2029 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
2031 case !is_array($authorityKey):
2032 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2033 $signingCert = $this->currentCert; // working cert
2037 if (!empty($this->CAs)) {
2038 for ($i = 0; $i < count($this->CAs); $i++) {
2039 // even if the cert is a self-signed one we still want to see if it's a CA;
2040 // if not, we'll conditionally return an error
2041 $ca = $this->CAs[$i];
2042 if ($this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
2043 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2044 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2046 case !is_array($authorityKey):
2047 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2048 $signingCert = $ca; // working cert
2053 if (count($this->CAs) == $i && $caonly) {
2056 } elseif (!isset($signingCert) || $caonly) {
2059 return $this->_validateSignature(
2060 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
2061 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
2062 $this->currentCert['signatureAlgorithm']['algorithm'],
2063 substr(base64_decode($this->currentCert['signature']), 1),
2064 $this->signatureSubject
2066 case isset($this->currentCert['certificationRequestInfo']):
2067 return $this->_validateSignature(
2068 $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
2069 $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
2070 $this->currentCert['signatureAlgorithm']['algorithm'],
2071 substr(base64_decode($this->currentCert['signature']), 1),
2072 $this->signatureSubject
2074 case isset($this->currentCert['publicKeyAndChallenge']):
2075 return $this->_validateSignature(
2076 $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
2077 $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
2078 $this->currentCert['signatureAlgorithm']['algorithm'],
2079 substr(base64_decode($this->currentCert['signature']), 1),
2080 $this->signatureSubject
2082 case isset($this->currentCert['tbsCertList']):
2083 if (!empty($this->CAs)) {
2084 for ($i = 0; $i < count($this->CAs); $i++) {
2085 $ca = $this->CAs[$i];
2086 if ($this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']) {
2087 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2088 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2090 case !is_array($authorityKey):
2091 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2092 $signingCert = $ca; // working cert
2098 if (!isset($signingCert)) {
2101 return $this->_validateSignature(
2102 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
2103 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
2104 $this->currentCert['signatureAlgorithm']['algorithm'],
2105 substr(base64_decode($this->currentCert['signature']), 1),
2106 $this->signatureSubject
2114 * Validates a signature
2116 * Returns true if the signature is verified, false if it is not correct or null on error
2118 * @param String $publicKeyAlgorithm
2119 * @param String $publicKey
2120 * @param String $signatureAlgorithm
2121 * @param String $signature
2122 * @param String $signatureSubject
2126 function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
2128 switch ($publicKeyAlgorithm) {
2129 case 'rsaEncryption':
2130 if (!class_exists('Crypt_RSA')) {
2131 include_once 'Crypt/RSA.php';
2133 $rsa = new Crypt_RSA();
2134 $rsa->loadKey($publicKey);
2136 switch ($signatureAlgorithm) {
2137 case 'md2WithRSAEncryption':
2138 case 'md5WithRSAEncryption':
2139 case 'sha1WithRSAEncryption':
2140 case 'sha224WithRSAEncryption':
2141 case 'sha256WithRSAEncryption':
2142 case 'sha384WithRSAEncryption':
2143 case 'sha512WithRSAEncryption':
2144 $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
2145 $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
2146 if (!@$rsa->verify($signatureSubject, $signature)) {
2162 * Reformat public keys
2164 * Reformats a public key to a format supported by phpseclib (if applicable)
2166 * @param String $algorithm
2167 * @param String $key
2171 function _reformatKey($algorithm, $key)
2173 switch ($algorithm) {
2174 case 'rsaEncryption':
2176 "-----BEGIN RSA PUBLIC KEY-----\r\n" .
2177 // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits
2178 // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox
2179 // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
2180 chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) .
2181 '-----END RSA PUBLIC KEY-----';
2188 * Decodes an IP address
2190 * Takes in a base64 encoded "blob" and returns a human readable IP address
2196 function _decodeIP($ip)
2198 $ip = base64_decode($ip);
2199 list(, $ip) = unpack('N', $ip);
2200 return long2ip($ip);
2204 * Encodes an IP address
2206 * Takes a human readable IP address into a base64-encoded "blob"
2212 function _encodeIP($ip)
2214 return base64_encode(pack('N', ip2long($ip)));
2218 * "Normalizes" a Distinguished Name property
2220 * @param String $propName
2224 function _translateDNProp($propName)
2226 switch (strtolower($propName)) {
2227 case 'id-at-countryname':
2230 return 'id-at-countryName';
2231 case 'id-at-organizationname':
2232 case 'organizationname':
2234 return 'id-at-organizationName';
2235 case 'id-at-dnqualifier':
2237 return 'id-at-dnQualifier';
2238 case 'id-at-commonname':
2241 return 'id-at-commonName';
2242 case 'id-at-stateorprovincename':
2243 case 'stateorprovincename':
2246 case 'provincename':
2248 return 'id-at-stateOrProvinceName';
2249 case 'id-at-localityname':
2250 case 'localityname':
2252 return 'id-at-localityName';
2253 case 'id-emailaddress':
2254 case 'emailaddress':
2255 return 'pkcs-9-at-emailAddress';
2256 case 'id-at-serialnumber':
2257 case 'serialnumber':
2258 return 'id-at-serialNumber';
2259 case 'id-at-postalcode':
2261 return 'id-at-postalCode';
2262 case 'id-at-streetaddress':
2263 case 'streetaddress':
2264 return 'id-at-streetAddress';
2267 return 'id-at-name';
2268 case 'id-at-givenname':
2270 return 'id-at-givenName';
2271 case 'id-at-surname':
2274 return 'id-at-surname';
2275 case 'id-at-initials':
2277 return 'id-at-initials';
2278 case 'id-at-generationqualifier':
2279 case 'generationqualifier':
2280 return 'id-at-generationQualifier';
2281 case 'id-at-organizationalunitname':
2282 case 'organizationalunitname':
2284 return 'id-at-organizationalUnitName';
2285 case 'id-at-pseudonym':
2287 return 'id-at-pseudonym';
2290 return 'id-at-title';
2291 case 'id-at-description':
2293 return 'id-at-description';
2296 return 'id-at-role';
2297 case 'id-at-uniqueidentifier':
2298 case 'uniqueidentifier':
2299 case 'x500uniqueidentifier':
2300 return 'id-at-uniqueIdentifier';
2307 * Set a Distinguished Name property
2309 * @param String $propName
2310 * @param Mixed $propValue
2311 * @param String $type optional
2315 function setDNProp($propName, $propValue, $type = 'utf8String')
2317 if (empty($this->dn)) {
2318 $this->dn = array('rdnSequence' => array());
2321 if (($propName = $this->_translateDNProp($propName)) === false) {
2325 foreach ((array) $propValue as $v) {
2326 if (!is_array($v) && isset($type)) {
2327 $v = array($type => $v);
2329 $this->dn['rdnSequence'][] = array(
2331 'type' => $propName,
2341 * Remove Distinguished Name properties
2343 * @param String $propName
2346 function removeDNProp($propName)
2348 if (empty($this->dn)) {
2352 if (($propName = $this->_translateDNProp($propName)) === false) {
2356 $dn = &$this->dn['rdnSequence'];
2358 for ($i = 0; $i < $size; $i++) {
2359 if ($dn[$i][0]['type'] == $propName) {
2364 $dn = array_values($dn);
2368 * Get Distinguished Name properties
2370 * @param String $propName
2371 * @param Array $dn optional
2372 * @param Boolean $withType optional
2376 function getDNProp($propName, $dn = null, $withType = false)
2386 if (($propName = $this->_translateDNProp($propName)) === false) {
2390 $dn = $dn['rdnSequence'];
2392 $asn1 = new File_ASN1();
2393 for ($i = 0; $i < count($dn); $i++) {
2394 if ($dn[$i][0]['type'] == $propName) {
2395 $v = $dn[$i][0]['value'];
2396 if (!$withType && is_array($v)) {
2397 foreach ($v as $type => $s) {
2398 $type = array_search($type, $asn1->ANYmap, true);
2399 if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2400 $s = $asn1->convert($s, $type);
2408 $v = array_pop($v); // Always strip data type.
2419 * Set a Distinguished Name
2422 * @param Boolean $merge optional
2423 * @param String $type optional
2427 function setDN($dn, $merge = false, $type = 'utf8String')
2433 if (is_array($dn)) {
2434 if (isset($dn['rdnSequence'])) {
2435 $this->dn = $dn; // No merge here.
2439 // handles stuff generated by openssl_x509_parse()
2440 foreach ($dn as $prop => $value) {
2441 if (!$this->setDNProp($prop, $value, $type)) {
2448 // handles everything else
2449 $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
2450 for ($i = 1; $i < count($results); $i+=2) {
2451 $prop = trim($results[$i], ', =/');
2452 $value = $results[$i + 1];
2453 if (!$this->setDNProp($prop, $value, $type)) {
2462 * Get the Distinguished Name for a certificates subject
2464 * @param Mixed $format optional
2465 * @param Array $dn optional
2469 function getDN($format = FILE_X509_DN_ARRAY, $dn = null)
2472 $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
2475 switch ((int) $format) {
2476 case FILE_X509_DN_ARRAY:
2478 case FILE_X509_DN_ASN1:
2479 $asn1 = new File_ASN1();
2480 $asn1->loadOIDs($this->oids);
2482 $filters['rdnSequence']['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
2483 $asn1->loadFilters($filters);
2484 return $asn1->encodeDER($dn, $this->Name);
2485 case FILE_X509_DN_OPENSSL:
2486 $dn = $this->getDN(FILE_X509_DN_STRING, $dn);
2487 if ($dn === false) {
2490 $attrs = preg_split('#((?:^|, *|/)[a-z][a-z0-9]*=)#i', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
2492 for ($i = 1; $i < count($attrs); $i += 2) {
2493 $prop = trim($attrs[$i], ', =/');
2494 $value = $attrs[$i + 1];
2495 if (!isset($dn[$prop])) {
2496 $dn[$prop] = $value;
2498 $dn[$prop] = array_merge((array) $dn[$prop], array($value));
2502 case FILE_X509_DN_CANON:
2503 // No SEQUENCE around RDNs and all string values normalized as
2504 // trimmed lowercase UTF-8 with all spacing as one blank.
2505 $asn1 = new File_ASN1();
2506 $asn1->loadOIDs($this->oids);
2508 $filters['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
2509 $asn1->loadFilters($filters);
2511 foreach ($dn['rdnSequence'] as $rdn) {
2512 foreach ($rdn as $i=>$attr) {
2514 if (is_array($attr['value'])) {
2515 foreach ($attr['value'] as $type => $v) {
2516 $type = array_search($type, $asn1->ANYmap, true);
2517 if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2518 $v = $asn1->convert($v, $type);
2520 $v = preg_replace('/\s+/', ' ', $v);
2521 $attr['value'] = strtolower(trim($v));
2528 $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName);
2531 case FILE_X509_DN_HASH:
2532 $dn = $this->getDN(FILE_X509_DN_CANON, $dn);
2533 if (!class_exists('Crypt_Hash')) {
2534 include_once 'Crypt/Hash.php';
2536 $hash = new Crypt_Hash('sha1');
2537 $hash = $hash->hash($dn);
2538 extract(unpack('Vhash', $hash));
2539 return strtolower(bin2hex(pack('N', $hash)));
2542 // Default is to return a string.
2545 $asn1 = new File_ASN1();
2546 foreach ($dn['rdnSequence'] as $field) {
2547 $prop = $field[0]['type'];
2548 $value = $field[0]['value'];
2552 case 'id-at-countryName':
2555 case 'id-at-stateOrProvinceName':
2558 case 'id-at-organizationName':
2561 case 'id-at-organizationalUnitName':
2564 case 'id-at-commonName':
2567 case 'id-at-localityName':
2570 case 'id-at-surname':
2573 case 'id-at-uniqueIdentifier':
2575 $desc = 'x500UniqueIdentifier=';
2579 $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop) . '=';
2585 if (is_array($value)) {
2586 foreach ($value as $type => $v) {
2587 $type = array_search($type, $asn1->ANYmap, true);
2588 if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2589 $v = $asn1->convert($v, $type);
2596 if (is_array($value)) {
2597 $value = array_pop($value); // Always strip data type.
2600 $output.= $desc . $value;
2608 * Get the Distinguished Name for a certificate/crl issuer
2610 * @param Integer $format optional
2614 function getIssuerDN($format = FILE_X509_DN_ARRAY)
2617 case !isset($this->currentCert) || !is_array($this->currentCert):
2619 case isset($this->currentCert['tbsCertificate']):
2620 return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
2621 case isset($this->currentCert['tbsCertList']):
2622 return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
2629 * Get the Distinguished Name for a certificate/csr subject
2632 * @param Integer $format optional
2636 function getSubjectDN($format = FILE_X509_DN_ARRAY)
2639 case !empty($this->dn):
2640 return $this->getDN($format);
2641 case !isset($this->currentCert) || !is_array($this->currentCert):
2643 case isset($this->currentCert['tbsCertificate']):
2644 return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
2645 case isset($this->currentCert['certificationRequestInfo']):
2646 return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
2653 * Get an individual Distinguished Name property for a certificate/crl issuer
2655 * @param String $propName
2656 * @param Boolean $withType optional
2660 function getIssuerDNProp($propName, $withType = false)
2663 case !isset($this->currentCert) || !is_array($this->currentCert):
2665 case isset($this->currentCert['tbsCertificate']):
2666 return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
2667 case isset($this->currentCert['tbsCertList']):
2668 return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
2675 * Get an individual Distinguished Name property for a certificate/csr subject
2677 * @param String $propName
2678 * @param Boolean $withType optional
2682 function getSubjectDNProp($propName, $withType = false)
2685 case !empty($this->dn):
2686 return $this->getDNProp($propName, null, $withType);
2687 case !isset($this->currentCert) || !is_array($this->currentCert):
2689 case isset($this->currentCert['tbsCertificate']):
2690 return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
2691 case isset($this->currentCert['certificationRequestInfo']):
2692 return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
2699 * Get the certificate chain for the current cert
2706 $chain = array($this->currentCert);
2708 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2711 if (empty($this->CAs)) {
2715 $currentCert = $chain[count($chain) - 1];
2716 for ($i = 0; $i < count($this->CAs); $i++) {
2717 $ca = $this->CAs[$i];
2718 if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
2719 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
2720 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2722 case !is_array($authorityKey):
2723 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2724 if ($currentCert === $ca) {
2732 if ($i == count($this->CAs)) {
2736 foreach ($chain as $key=>$value) {
2737 $chain[$key] = new File_X509();
2738 $chain[$key]->loadX509($value);
2746 * Key needs to be a Crypt_RSA object
2748 * @param Object $key
2752 function setPublicKey($key)
2754 $key->setPublicKey();
2755 $this->publicKey = $key;
2761 * Key needs to be a Crypt_RSA object
2763 * @param Object $key
2766 function setPrivateKey($key)
2768 $this->privateKey = $key;
2774 * Used for SPKAC CSR's
2776 * @param String $challenge
2779 function setChallenge($challenge)
2781 $this->challenge = $challenge;
2785 * Gets the public key
2787 * Returns a Crypt_RSA object or a false.
2792 function getPublicKey()
2794 if (isset($this->publicKey)) {
2795 return $this->publicKey;
2798 if (isset($this->currentCert) && is_array($this->currentCert)) {
2799 foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
2800 $keyinfo = $this->_subArray($this->currentCert, $path);
2801 if (!empty($keyinfo)) {
2806 if (empty($keyinfo)) {
2810 $key = $keyinfo['subjectPublicKey'];
2812 switch ($keyinfo['algorithm']['algorithm']) {
2813 case 'rsaEncryption':
2814 if (!class_exists('Crypt_RSA')) {
2815 include_once 'Crypt/RSA.php';
2817 $publicKey = new Crypt_RSA();
2818 $publicKey->loadKey($key);
2819 $publicKey->setPublicKey();
2829 * Load a Certificate Signing Request
2831 * @param String $csr
2835 function loadCSR($csr)
2837 if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
2838 unset($this->currentCert);
2839 unset($this->currentKeyIdentifier);
2840 unset($this->signatureSubject);
2841 $this->dn = $csr['certificationRequestInfo']['subject'];
2842 if (!isset($this->dn)) {
2846 $this->currentCert = $csr;
2850 // see http://tools.ietf.org/html/rfc2986
2852 $asn1 = new File_ASN1();
2854 $csr = $this->_extractBER($csr);
2857 if ($csr === false) {
2858 $this->currentCert = false;
2862 $asn1->loadOIDs($this->oids);
2863 $decoded = $asn1->decodeBER($csr);
2865 if (empty($decoded)) {
2866 $this->currentCert = false;
2870 $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
2871 if (!isset($csr) || $csr === false) {
2872 $this->currentCert = false;
2876 $this->dn = $csr['certificationRequestInfo']['subject'];
2877 $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
2879 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
2881 $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
2882 $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
2883 $key = $this->_reformatKey($algorithm, $key);
2885 switch ($algorithm) {
2886 case 'rsaEncryption':
2887 if (!class_exists('Crypt_RSA')) {
2888 include_once 'Crypt/RSA.php';
2890 $this->publicKey = new Crypt_RSA();
2891 $this->publicKey->loadKey($key);
2892 $this->publicKey->setPublicKey();
2895 $this->publicKey = null;
2898 $this->currentKeyIdentifier = null;
2899 $this->currentCert = $csr;
2908 * @param Integer $format optional
2912 function saveCSR($csr, $format = FILE_X509_FORMAT_PEM)
2914 if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
2919 case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
2920 case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']);
2923 switch ($algorithm) {
2924 case 'rsaEncryption':
2925 $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
2926 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
2930 $asn1 = new File_ASN1();
2932 $asn1->loadOIDs($this->oids);
2935 $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
2936 = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
2938 $asn1->loadFilters($filters);
2940 $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
2941 $csr = $asn1->encodeDER($csr, $this->CertificationRequest);
2944 case FILE_X509_FORMAT_DER:
2946 // case FILE_X509_FORMAT_PEM:
2948 return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
2955 * SPKAC's are produced by the HTML5 keygen element:
2957 * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
2959 * @param String $csr
2963 function loadSPKAC($spkac)
2965 if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
2966 unset($this->currentCert);
2967 unset($this->currentKeyIdentifier);
2968 unset($this->signatureSubject);
2969 $this->currentCert = $spkac;
2973 // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
2975 $asn1 = new File_ASN1();
2977 // OpenSSL produces SPKAC's that are preceeded by the string SPKAC=
2978 $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
2979 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
2980 if ($temp != false) {
2985 if ($spkac === false) {
2986 $this->currentCert = false;
2990 $asn1->loadOIDs($this->oids);
2991 $decoded = $asn1->decodeBER($spkac);
2993 if (empty($decoded)) {
2994 $this->currentCert = false;
2998 $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge);
3000 if (!isset($spkac) || $spkac === false) {
3001 $this->currentCert = false;
3005 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3007 $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
3008 $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
3009 $key = $this->_reformatKey($algorithm, $key);
3011 switch ($algorithm) {
3012 case 'rsaEncryption':
3013 if (!class_exists('Crypt_RSA')) {
3014 include_once 'Crypt/RSA.php';
3016 $this->publicKey = new Crypt_RSA();
3017 $this->publicKey->loadKey($key);
3018 $this->publicKey->setPublicKey();
3021 $this->publicKey = null;
3024 $this->currentKeyIdentifier = null;
3025 $this->currentCert = $spkac;
3031 * Save a SPKAC CSR request
3034 * @param Integer $format optional
3038 function saveSPKAC($spkac, $format = FILE_X509_FORMAT_PEM)
3040 if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
3044 $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
3047 case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']);
3050 switch ($algorithm) {
3051 case 'rsaEncryption':
3052 $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
3053 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
3057 $asn1 = new File_ASN1();
3059 $asn1->loadOIDs($this->oids);
3060 $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);
3063 case FILE_X509_FORMAT_DER:
3065 // case FILE_X509_FORMAT_PEM:
3067 // OpenSSL's implementation of SPKAC requires the SPKAC be preceeded by SPKAC= and since there are pretty much
3068 // no other SPKAC decoders phpseclib will use that same format
3069 return 'SPKAC=' . base64_encode($spkac);
3074 * Load a Certificate Revocation List
3076 * @param String $crl
3080 function loadCRL($crl)
3082 if (is_array($crl) && isset($crl['tbsCertList'])) {
3083 $this->currentCert = $crl;
3084 unset($this->signatureSubject);
3088 $asn1 = new File_ASN1();
3090 $crl = $this->_extractBER($crl);
3093 if ($crl === false) {
3094 $this->currentCert = false;
3098 $asn1->loadOIDs($this->oids);
3099 $decoded = $asn1->decodeBER($crl);
3101 if (empty($decoded)) {
3102 $this->currentCert = false;
3106 $crl = $asn1->asn1map($decoded[0], $this->CertificateList);
3107 if (!isset($crl) || $crl === false) {
3108 $this->currentCert = false;
3112 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3114 $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
3115 $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
3116 if (is_array($rclist)) {
3117 foreach ($rclist as $i => $extension) {
3118 $this->_mapInExtensions($rclist, "$i/crlEntryExtensions", $asn1);
3122 $this->currentKeyIdentifier = null;
3123 $this->currentCert = $crl;
3129 * Save Certificate Revocation List.
3132 * @param Integer $format optional
3136 function saveCRL($crl, $format = FILE_X509_FORMAT_PEM)
3138 if (!is_array($crl) || !isset($crl['tbsCertList'])) {
3142 $asn1 = new File_ASN1();
3144 $asn1->loadOIDs($this->oids);
3147 $filters['tbsCertList']['issuer']['rdnSequence']['value']
3148 = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
3149 $filters['tbsCertList']['signature']['parameters']
3150 = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
3151 $filters['signatureAlgorithm']['parameters']
3152 = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
3154 if (empty($crl['tbsCertList']['signature']['parameters'])) {
3155 $filters['tbsCertList']['signature']['parameters']
3156 = array('type' => FILE_ASN1_TYPE_NULL);
3159 if (empty($crl['signatureAlgorithm']['parameters'])) {
3160 $filters['signatureAlgorithm']['parameters']
3161 = array('type' => FILE_ASN1_TYPE_NULL);
3164 $asn1->loadFilters($filters);
3166 $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
3167 $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
3168 if (is_array($rclist)) {
3169 foreach ($rclist as $i => $extension) {
3170 $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
3174 $crl = $asn1->encodeDER($crl, $this->CertificateList);
3177 case FILE_X509_FORMAT_DER:
3179 // case FILE_X509_FORMAT_PEM:
3181 return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----';
3186 * Helper function to build a time field according to RFC 3280 section
3187 * - 4.1.2.5 Validity
3188 * - 5.1.2.4 This Update
3189 * - 5.1.2.5 Next Update
3190 * - 5.1.2.6 Revoked Certificates
3191 * by choosing utcTime iff year of date given is before 2050 and generalTime else.
3193 * @param String $date in format date('D, d M Y H:i:s O')
3197 function _timeField($date)
3199 $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
3201 return array('utcTime' => $date);
3203 return array('generalTime' => $date);
3208 * Sign an X.509 certificate
3210 * $issuer's private key needs to be loaded.
3211 * $subject can be either an existing X.509 cert (if you want to resign it),
3212 * a CSR or something with the DN and public key explicitly set.
3214 * @param File_X509 $issuer
3215 * @param File_X509 $subject
3216 * @param String $signatureAlgorithm optional
3220 function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
3222 if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
3226 if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
3230 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3231 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3233 if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
3234 $this->currentCert = $subject->currentCert;
3235 $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
3236 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3238 if (!empty($this->startDate)) {
3239 $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
3241 if (!empty($this->endDate)) {
3242 $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
3244 if (!empty($this->serialNumber)) {
3245 $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
3247 if (!empty($subject->dn)) {
3248 $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
3250 if (!empty($subject->publicKey)) {
3251 $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
3253 $this->removeExtension('id-ce-authorityKeyIdentifier');
3254 if (isset($subject->domains)) {
3255 $this->removeExtension('id-ce-subjectAltName');
3257 } else if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
3260 if (!isset($subject->publicKey)) {
3264 $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
3265 $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year'));
3266 $serialNumber = !empty($this->serialNumber) ? $this->serialNumber : new Math_BigInteger();
3268 $this->currentCert = array(
3272 'serialNumber' => $serialNumber, // $this->setserialNumber()
3273 'signature' => array('algorithm' => $signatureAlgorithm),
3274 'issuer' => false, // this is going to be overwritten later
3275 'validity' => array(
3276 'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
3277 'notAfter' => $this->_timeField($endDate) // $this->setEndDate()
3279 'subject' => $subject->dn,
3280 'subjectPublicKeyInfo' => $subjectPublicKey
3282 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3283 'signature' => false // this is going to be overwritten later
3286 // Copy extensions from CSR.
3287 $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
3289 if (!empty($csrexts)) {
3290 $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
3294 $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
3296 if (isset($issuer->currentKeyIdentifier)) {
3297 $this->setExtension('id-ce-authorityKeyIdentifier', array(
3298 //'authorityCertIssuer' => array(
3300 // 'directoryName' => $issuer->dn
3303 'keyIdentifier' => $issuer->currentKeyIdentifier
3306 //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
3307 //if (isset($issuer->serialNumber)) {
3308 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3310 //unset($extensions);
3313 if (isset($subject->currentKeyIdentifier)) {
3314 $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
3319 if (isset($subject->domains) && count($subject->domains) > 1) {
3320 $altName = array_map(array('File_X509', '_dnsName'), $subject->domains);
3323 if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
3324 // should an IP address appear as the CN if no domain name is specified? idk
3325 //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
3326 $ipAddresses = array();
3327 foreach ($subject->ipAddresses as $ipAddress) {
3328 $encoded = $subject->_ipAddress($ipAddress);
3329 if ($encoded !== false) {
3330 $ipAddresses[] = $encoded;
3333 if (count($ipAddresses)) {
3334 $altName = array_merge($altName, $ipAddresses);
3338 if (!empty($altName)) {
3339 $this->setExtension('id-ce-subjectAltName', $altName);
3342 if ($this->caFlag) {
3343 $keyUsage = $this->getExtension('id-ce-keyUsage');
3345 $keyUsage = array();
3348 $this->setExtension('id-ce-keyUsage',
3349 array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
3352 $basicConstraints = $this->getExtension('id-ce-basicConstraints');
3353 if (!$basicConstraints) {
3354 $basicConstraints = array();
3357 $this->setExtension('id-ce-basicConstraints',
3358 array_unique(array_merge(array('cA' => true), $basicConstraints)), true);
3360 if (!isset($subject->currentKeyIdentifier)) {
3361 $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false);
3365 // resync $this->signatureSubject
3366 // save $tbsCertificate in case there are any File_ASN1_Element objects in it
3367 $tbsCertificate = $this->currentCert['tbsCertificate'];
3368 $this->loadX509($this->saveX509($this->currentCert));
3370 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
3371 $result['tbsCertificate'] = $tbsCertificate;
3373 $this->currentCert = $currentCert;
3374 $this->signatureSubject = $signatureSubject;
3385 function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
3387 if (!is_object($this->privateKey) || empty($this->dn)) {
3391 $origPublicKey = $this->publicKey;
3392 $class = get_class($this->privateKey);
3393 $this->publicKey = new $class();
3394 $this->publicKey->loadKey($this->privateKey->getPublicKey());
3395 $this->publicKey->setPublicKey();
3396 if (!($publicKey = $this->_formatSubjectPublicKey())) {
3399 $this->publicKey = $origPublicKey;
3401 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3402 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3404 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
3405 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3406 if (!empty($this->dn)) {
3407 $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
3409 $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
3411 $this->currentCert = array(
3412 'certificationRequestInfo' =>
3415 'subject' => $this->dn,
3416 'subjectPKInfo' => $publicKey
3418 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3419 'signature' => false // this is going to be overwritten later
3423 // resync $this->signatureSubject
3424 // save $certificationRequestInfo in case there are any File_ASN1_Element objects in it
3425 $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
3426 $this->loadCSR($this->saveCSR($this->currentCert));
3428 $result = $this->_sign($this->privateKey, $signatureAlgorithm);
3429 $result['certificationRequestInfo'] = $certificationRequestInfo;
3431 $this->currentCert = $currentCert;
3432 $this->signatureSubject = $signatureSubject;
3443 function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
3445 if (!is_object($this->privateKey)) {
3449 $origPublicKey = $this->publicKey;
3450 $class = get_class($this->privateKey);
3451 $this->publicKey = new $class();
3452 $this->publicKey->loadKey($this->privateKey->getPublicKey());
3453 $this->publicKey->setPublicKey();
3454 $publicKey = $this->_formatSubjectPublicKey();
3458 $this->publicKey = $origPublicKey;
3460 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3461 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3463 // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
3464 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
3465 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3466 $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
3467 if (!empty($this->challenge)) {
3468 // the bitwise AND ensures that the output is a valid IA5String
3469 $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
3472 $this->currentCert = array(
3473 'publicKeyAndChallenge' =>
3475 'spki' => $publicKey,
3476 // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
3477 // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
3478 // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
3479 // we could alternatively do this instead if we ignored the specs:
3480 // crypt_random_string(8) & str_repeat("\x7F", 8)
3481 'challenge' => !empty($this->challenge) ? $this->challenge : ''
3483 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3484 'signature' => false // this is going to be overwritten later
3488 // resync $this->signatureSubject
3489 // save $publicKeyAndChallenge in case there are any File_ASN1_Element objects in it
3490 $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
3491 $this->loadSPKAC($this->saveSPKAC($this->currentCert));
3493 $result = $this->_sign($this->privateKey, $signatureAlgorithm);
3494 $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
3496 $this->currentCert = $currentCert;
3497 $this->signatureSubject = $signatureSubject;
3505 * $issuer's private key needs to be loaded.
3507 * @param File_X509 $issuer
3508 * @param File_X509 $crl
3509 * @param String $signatureAlgorithm optional
3513 function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
3515 if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
3519 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3520 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
3521 $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
3523 if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
3524 $this->currentCert = $crl->currentCert;
3525 $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
3526 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3528 $this->currentCert = array(
3532 'signature' => array('algorithm' => $signatureAlgorithm),
3533 'issuer' => false, // this is going to be overwritten later
3534 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
3536 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3537 'signature' => false // this is going to be overwritten later
3541 $tbsCertList = &$this->currentCert['tbsCertList'];
3542 $tbsCertList['issuer'] = $issuer->dn;
3543 $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);
3545 if (!empty($this->endDate)) {
3546 $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
3548 unset($tbsCertList['nextUpdate']);
3551 if (!empty($this->serialNumber)) {
3552 $crlNumber = $this->serialNumber;
3554 $crlNumber = $this->getExtension('id-ce-cRLNumber');
3555 $crlNumber = $crlNumber !== false ? $crlNumber->add(new Math_BigInteger(1)) : null;
3558 $this->removeExtension('id-ce-authorityKeyIdentifier');
3559 $this->removeExtension('id-ce-issuerAltName');
3561 // Be sure version >= v2 if some extension found.
3562 $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
3564 if (!empty($tbsCertList['crlExtensions'])) {
3565 $version = 1; // v2.
3566 } elseif (!empty($tbsCertList['revokedCertificates'])) {
3567 foreach ($tbsCertList['revokedCertificates'] as $cert) {
3568 if (!empty($cert['crlEntryExtensions'])) {
3569 $version = 1; // v2.
3575 $tbsCertList['version'] = $version;
3579 // Store additional extensions.
3580 if (!empty($tbsCertList['version'])) { // At least v2.
3581 if (!empty($crlNumber)) {
3582 $this->setExtension('id-ce-cRLNumber', $crlNumber);
3585 if (isset($issuer->currentKeyIdentifier)) {
3586 $this->setExtension('id-ce-authorityKeyIdentifier', array(
3587 //'authorityCertIssuer' => array(
3589 // 'directoryName' => $issuer->dn
3592 'keyIdentifier' => $issuer->currentKeyIdentifier
3595 //$extensions = &$tbsCertList['crlExtensions'];
3596 //if (isset($issuer->serialNumber)) {
3597 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3599 //unset($extensions);
3602 $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);
3604 if ($issuerAltName !== false) {
3605 $this->setExtension('id-ce-issuerAltName', $issuerAltName);
3609 if (empty($tbsCertList['revokedCertificates'])) {
3610 unset($tbsCertList['revokedCertificates']);
3613 unset($tbsCertList);
3615 // resync $this->signatureSubject
3616 // save $tbsCertList in case there are any File_ASN1_Element objects in it
3617 $tbsCertList = $this->currentCert['tbsCertList'];
3618 $this->loadCRL($this->saveCRL($this->currentCert));
3620 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
3621 $result['tbsCertList'] = $tbsCertList;
3623 $this->currentCert = $currentCert;
3624 $this->signatureSubject = $signatureSubject;
3630 * X.509 certificate signing helper function.
3632 * @param Object $key
3633 * @param File_X509 $subject
3634 * @param String $signatureAlgorithm
3638 function _sign($key, $signatureAlgorithm)
3640 switch (strtolower(get_class($key))) {
3642 switch ($signatureAlgorithm) {
3643 case 'md2WithRSAEncryption':
3644 case 'md5WithRSAEncryption':
3645 case 'sha1WithRSAEncryption':
3646 case 'sha224WithRSAEncryption':
3647 case 'sha256WithRSAEncryption':
3648 case 'sha384WithRSAEncryption':
3649 case 'sha512WithRSAEncryption':
3650 $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
3651 $key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
3653 $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
3654 return $this->currentCert;
3662 * Set certificate start date
3664 * @param String $date
3667 function setStartDate($date)
3669 $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
3673 * Set certificate end date
3675 * @param String $date
3678 function setEndDate($date)
3681 To indicate that a certificate has no well-defined expiration date,
3682 the notAfter SHOULD be assigned the GeneralizedTime value of
3685 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
3687 if (strtolower($date) == 'lifetime') {
3688 $temp = '99991231235959Z';
3689 $asn1 = new File_ASN1();
3690 $temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
3691 $this->endDate = new File_ASN1_Element($temp);
3693 $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
3700 * @param String $serial
3701 * @param $base optional
3704 function setSerialNumber($serial, $base = -256)
3706 $this->serialNumber = new Math_BigInteger($serial, $base);
3710 * Turns the certificate into a certificate authority
3716 $this->caFlag = true;
3720 * Get a reference to a subarray
3722 * @param array $root
3723 * @param String $path absolute path with / as component separator
3724 * @param Boolean $create optional
3726 * @return array item ref or false
3728 function &_subArray(&$root, $path, $create = false)
3732 if (!is_array($root)) {
3736 foreach (explode('/', $path) as $i) {
3737 if (!is_array($root)) {
3741 if (!isset($root[$i])) {
3746 $root[$i] = array();
3756 * Get a reference to an extension subarray
3758 * @param array $root
3759 * @param String $path optional absolute path with / as component separator
3760 * @param Boolean $create optional
3762 * @return array ref or false
3764 function &_extensions(&$root, $path = null, $create = false)
3766 if (!isset($root)) {
3767 $root = $this->currentCert;
3772 case !is_array($root):
3774 case isset($root['tbsCertificate']):
3775 $path = 'tbsCertificate/extensions';
3777 case isset($root['tbsCertList']):
3778 $path = 'tbsCertList/crlExtensions';
3780 case isset($root['certificationRequestInfo']):
3781 $pth = 'certificationRequestInfo/attributes';
3782 $attributes = &$this->_subArray($root, $pth, $create);
3784 if (is_array($attributes)) {
3785 foreach ($attributes as $key => $value) {
3786 if ($value['type'] == 'pkcs-9-at-extensionRequest') {
3787 $path = "$pth/$key/value/0";
3792 $key = count($attributes);
3793 $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array());
3794 $path = "$pth/$key/value/0";
3800 $extensions = &$this->_subArray($root, $path, $create);
3802 if (!is_array($extensions)) {
3811 * Remove an Extension
3814 * @param String $path optional
3818 function _removeExtension($id, $path = null)
3820 $extensions = &$this->_extensions($this->currentCert, $path);
3822 if (!is_array($extensions)) {
3827 foreach ($extensions as $key => $value) {
3828 if ($value['extnId'] == $id) {
3829 unset($extensions[$key]);
3834 $extensions = array_values($extensions);
3841 * Returns the extension if it exists and false if not
3844 * @param Array $cert optional
3845 * @param String $path optional
3849 function _getExtension($id, $cert = null, $path = null)
3851 $extensions = $this->_extensions($cert, $path);
3853 if (!is_array($extensions)) {
3857 foreach ($extensions as $key => $value) {
3858 if ($value['extnId'] == $id) {
3859 return $value['extnValue'];
3867 * Returns a list of all extensions in use
3869 * @param array $cert optional
3870 * @param String $path optional
3874 function _getExtensions($cert = null, $path = null)
3876 $exts = $this->_extensions($cert, $path);
3877 $extensions = array();
3879 if (is_array($exts)) {
3880 foreach ($exts as $extension) {
3881 $extensions[] = $extension['extnId'];
3892 * @param Mixed $value
3893 * @param Boolean $critical optional
3894 * @param Boolean $replace optional
3895 * @param String $path optional
3899 function _setExtension($id, $value, $critical = false, $replace = true, $path = null)
3901 $extensions = &$this->_extensions($this->currentCert, $path, true);
3903 if (!is_array($extensions)) {
3907 $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value);
3909 foreach ($extensions as $key => $value) {
3910 if ($value['extnId'] == $id) {
3915 $extensions[$key] = $newext;
3920 $extensions[] = $newext;
3925 * Remove a certificate, CSR or CRL Extension
3931 function removeExtension($id)
3933 return $this->_removeExtension($id);
3937 * Get a certificate, CSR or CRL Extension
3939 * Returns the extension if it exists and false if not
3942 * @param Array $cert optional
3946 function getExtension($id, $cert = null)
3948 return $this->_getExtension($id, $cert);
3952 * Returns a list of all extensions in use in certificate, CSR or CRL
3954 * @param array $cert optional
3958 function getExtensions($cert = null)
3960 return $this->_getExtensions($cert);
3964 * Set a certificate, CSR or CRL Extension
3967 * @param Mixed $value
3968 * @param Boolean $critical optional
3969 * @param Boolean $replace optional
3973 function setExtension($id, $value, $critical = false, $replace = true)
3975 return $this->_setExtension($id, $value, $critical, $replace);
3979 * Remove a CSR attribute.
3982 * @param Integer $disposition optional
3986 function removeAttribute($id, $disposition = FILE_X509_ATTR_ALL)
3988 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes');
3990 if (!is_array($attributes)) {
3995 foreach ($attributes as $key => $attribute) {
3996 if ($attribute['type'] == $id) {
3997 $n = count($attribute['value']);
3999 case $disposition == FILE_X509_ATTR_APPEND:
4000 case $disposition == FILE_X509_ATTR_REPLACE:
4002 case $disposition >= $n:
4005 case $disposition == FILE_X509_ATTR_ALL:
4007 unset($attributes[$key]);
4011 unset($attributes[$key]['value'][$disposition]);
4012 $attributes[$key]['value'] = array_values($attributes[$key]['value']);
4016 if ($result && $disposition != FILE_X509_ATTR_ALL) {
4022 $attributes = array_values($attributes);
4027 * Get a CSR attribute
4029 * Returns the attribute if it exists and false if not
4032 * @param Integer $disposition optional
4033 * @param Array $csr optional
4037 function getAttribute($id, $disposition = FILE_X509_ATTR_ALL, $csr = null)
4040 $csr = $this->currentCert;
4043 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
4045 if (!is_array($attributes)) {
4049 foreach ($attributes as $key => $attribute) {
4050 if ($attribute['type'] == $id) {
4051 $n = count($attribute['value']);
4053 case $disposition == FILE_X509_ATTR_APPEND:
4054 case $disposition == FILE_X509_ATTR_REPLACE:
4056 case $disposition == FILE_X509_ATTR_ALL:
4057 return $attribute['value'];
4058 case $disposition >= $n:
4062 return $attribute['value'][$disposition];
4071 * Returns a list of all CSR attributes in use
4073 * @param array $csr optional
4077 function getAttributes($csr = null)
4080 $csr = $this->currentCert;
4083 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
4086 if (is_array($attributes)) {
4087 foreach ($attributes as $attribute) {
4088 $attrs[] = $attribute['type'];
4096 * Set a CSR attribute
4099 * @param Mixed $value
4100 * @param Boolean $disposition optional
4104 function setAttribute($id, $value, $disposition = FILE_X509_ATTR_ALL)
4106 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
4108 if (!is_array($attributes)) {
4112 switch ($disposition) {
4113 case FILE_X509_ATTR_REPLACE:
4114 $disposition = FILE_X509_ATTR_APPEND;
4115 case FILE_X509_ATTR_ALL:
4116 $this->removeAttribute($id);
4120 foreach ($attributes as $key => $attribute) {
4121 if ($attribute['type'] == $id) {
4122 $n = count($attribute['value']);
4124 case $disposition == FILE_X509_ATTR_APPEND:
4127 case $disposition >= $n;
4131 $attributes[$key]['value'][$disposition] = $value;
4138 case $disposition >= 0:
4141 $attributes[$last]['value'][] = $value;
4144 $attributes[] = array('type' => $id, 'value' => $disposition == FILE_X509_ATTR_ALL ? $value: array($value));
4152 * Sets the subject key identifier
4154 * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
4156 * @param String $value
4159 function setKeyIdentifier($value)
4161 if (empty($value)) {
4162 unset($this->currentKeyIdentifier);
4164 $this->currentKeyIdentifier = base64_encode($value);
4169 * Compute a public key identifier.
4171 * Although key identifiers may be set to any unique value, this function
4172 * computes key identifiers from public key according to the two
4173 * recommended methods (4.2.1.2 RFC 3280).
4174 * Highly polymorphic: try to accept all possible forms of key:
4176 * - File_X509 object with public or private key defined
4177 * - Certificate or CSR array
4178 * - File_ASN1_Element object
4179 * - PEM or DER string
4181 * @param Mixed $key optional
4182 * @param Integer $method optional
4184 * @return String binary key identifier
4186 function computeKeyIdentifier($key = null, $method = 1)
4188 if (is_null($key)) {
4193 case is_string($key):
4195 case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
4196 return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
4197 case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
4198 return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
4199 case !is_object($key):
4201 case strtolower(get_class($key)) == 'file_asn1_element':
4202 // Assume the element is a bitstring-packed key.
4203 $asn1 = new File_ASN1();
4204 $decoded = $asn1->decodeBER($key->element);
4205 if (empty($decoded)) {
4208 $raw = $asn1->asn1map($decoded[0], array('type' => FILE_ASN1_TYPE_BIT_STRING));
4212 $raw = base64_decode($raw);
4213 // If the key is private, compute identifier from its corresponding public key.
4214 if (!class_exists('Crypt_RSA')) {
4215 include_once 'Crypt/RSA.php';
4217 $key = new Crypt_RSA();
4218 if (!$key->loadKey($raw)) {
4219 return false; // Not an unencrypted RSA key.
4221 if ($key->getPrivateKey() !== false) { // If private.
4222 return $this->computeKeyIdentifier($key, $method);
4224 $key = $raw; // Is a public key.
4226 case strtolower(get_class($key)) == 'file_x509':
4227 if (isset($key->publicKey)) {
4228 return $this->computeKeyIdentifier($key->publicKey, $method);
4230 if (isset($key->privateKey)) {
4231 return $this->computeKeyIdentifier($key->privateKey, $method);
4233 if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
4234 return $this->computeKeyIdentifier($key->currentCert, $method);
4237 default: // Should be a key object (i.e.: Crypt_RSA).
4238 $key = $key->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
4242 // If in PEM format, convert to binary.
4243 $key = $this->_extractBER($key);
4245 // Now we have the key string: compute its sha-1 sum.
4246 if (!class_exists('Crypt_Hash')) {
4247 include_once 'Crypt/Hash.php';
4249 $hash = new Crypt_Hash('sha1');
4250 $hash = $hash->hash($key);
4253 $hash = substr($hash, -8);
4254 $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
4261 * Format a public key as appropriate
4266 function _formatSubjectPublicKey()
4268 if (!isset($this->publicKey) || !is_object($this->publicKey)) {
4272 switch (strtolower(get_class($this->publicKey))) {
4274 // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
4275 // the former is a good example of how to do fuzzing on the public key
4276 //return new File_ASN1_Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())));
4278 'algorithm' => array('algorithm' => 'rsaEncryption'),
4279 'subjectPublicKey' => $this->publicKey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1)
4287 * Set the domain name's which the cert is to be valid for
4292 function setDomain()
4294 $this->domains = func_get_args();
4295 $this->removeDNProp('id-at-commonName');
4296 $this->setDNProp('id-at-commonName', $this->domains[0]);
4300 * Set the IP Addresses's which the cert is to be valid for
4303 * @param String $ipAddress optional
4305 function setIPAddress()
4307 $this->ipAddresses = func_get_args();
4309 if (!isset($this->domains)) {
4310 $this->removeDNProp('id-at-commonName');
4311 $this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
4317 * Helper function to build domain array
4320 * @param String $domain
4323 function _dnsName($domain)
4325 return array('dNSName' => $domain);
4329 * Helper function to build IP Address array
4331 * (IPv6 is not currently supported)
4334 * @param String $address
4337 function _iPAddress($address)
4339 return array('iPAddress' => $address);
4343 * Get the index of a revoked certificate.
4345 * @param array $rclist
4346 * @param String $serial
4347 * @param Boolean $create optional
4349 * @return Integer or false
4351 function _revokedCertificate(&$rclist, $serial, $create = false)
4353 $serial = new Math_BigInteger($serial);
4355 foreach ($rclist as $i => $rc) {
4356 if (!($serial->compare($rc['userCertificate']))) {
4365 $i = count($rclist);
4366 $rclist[] = array('userCertificate' => $serial,
4367 'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O')));
4372 * Revoke a certificate.
4374 * @param String $serial
4375 * @param String $date optional
4379 function revoke($serial, $date = null)
4381 if (isset($this->currentCert['tbsCertList'])) {
4382 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
4383 if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked
4384 if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
4386 if (!empty($date)) {
4387 $rclist[$i]['revocationDate'] = $this->_timeField($date);
4400 * Unrevoke a certificate.
4402 * @param String $serial
4406 function unrevoke($serial)
4408 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
4409 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4411 $rclist = array_values($rclist);
4420 * Get a revoked certificate.
4422 * @param String $serial
4426 function getRevoked($serial)
4428 if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
4429 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4438 * List revoked certificates
4440 * @param array $crl optional
4444 function listRevoked($crl = null)
4447 $crl = $this->currentCert;
4450 if (!isset($crl['tbsCertList'])) {
4456 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
4457 foreach ($rclist as $rc) {
4458 $result[] = $rc['userCertificate']->toString();
4466 * Remove a Revoked Certificate Extension
4468 * @param String $serial
4473 function removeRevokedCertificateExtension($serial, $id)
4475 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
4476 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4477 return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4485 * Get a Revoked Certificate Extension
4487 * Returns the extension if it exists and false if not
4489 * @param String $serial
4491 * @param Array $crl optional
4495 function getRevokedCertificateExtension($serial, $id, $crl = null)
4498 $crl = $this->currentCert;
4501 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
4502 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4503 return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4511 * Returns a list of all extensions in use for a given revoked certificate
4513 * @param String $serial
4514 * @param array $crl optional
4518 function getRevokedCertificateExtensions($serial, $crl = null)
4521 $crl = $this->currentCert;
4524 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
4525 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4526 return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4534 * Set a Revoked Certificate Extension
4536 * @param String $serial
4538 * @param Mixed $value
4539 * @param Boolean $critical optional
4540 * @param Boolean $replace optional
4544 function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
4546 if (isset($this->currentCert['tbsCertList'])) {
4547 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
4548 if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
4549 return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4558 * Extract raw BER from Base64 encoding
4561 * @param String $str
4564 function _extractBER($str)
4566 /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
4567 * above and beyond the ceritificate.
4568 * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
4571 * localKeyID: 01 00 00 00
4572 * subject=/O=organization/OU=org unit/CN=common name
4573 * issuer=/O=organization/CN=common name
4575 $temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $str, 1);
4576 // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
4577 $temp = preg_replace('#-+[^-]+-+#', '', $temp);
4579 $temp = str_replace(array("\r", "\n", ' '), '', $temp);
4580 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
4581 return $temp != false ? $temp : $str;