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.
21 * @author Jim Wigginton <terrafrost@php.net>
22 * @copyright 2012 Jim Wigginton
23 * @license http://www.opensource.org/licenses/mit-license.html MIT License
24 * @link http://phpseclib.sourceforge.net
27 namespace phpseclib\File;
29 use ParagonIE\ConstantTime\Base64;
30 use ParagonIE\ConstantTime\Hex;
31 use phpseclib\Crypt\Hash;
32 use phpseclib\Crypt\Random;
33 use phpseclib\Crypt\RSA;
34 use phpseclib\Exception\UnsupportedAlgorithmException;
35 use phpseclib\File\ASN1\Element;
36 use phpseclib\Math\BigInteger;
39 * Pure-PHP X.509 Parser
42 * @author Jim Wigginton <terrafrost@php.net>
48 * Flag to only accept signatures signed by certificate authorities
50 * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
54 const VALIDATE_SIGNATURE_BY_CA = 1;
58 * @see \phpseclib\File\X509::getDN()
61 * Return internal array representation
69 * Return ASN.1 name string
73 * Return OpenSSL compatible array
77 * Return canonical ASN.1 RDNs string
81 * Return name hash for file indexing
88 * @see \phpseclib\File\X509::saveX509()
89 * @see \phpseclib\File\X509::saveCSR()
90 * @see \phpseclib\File\X509::saveCRL()
95 * ie. a base64-encoded PEM with a header and a footer
101 const FORMAT_DER = 1;
105 * Only works on CSRs. Not currently supported.
107 const FORMAT_SPKAC = 2;
109 * Auto-detect the format
111 * Used only by the load*() functions
113 const FORMAT_AUTO_DETECT = 3;
117 * Attribute value disposition.
118 * If disposition is >= 0, this is the index of the target value.
120 const ATTR_ALL = -1; // All attribute values (array).
121 const ATTR_APPEND = -2; // Add a value.
122 const ATTR_REPLACE = -3; // Clear first, then add a value.
125 * ASN.1 syntax for X.509 certificates
133 * ASN.1 syntax for various extensions
137 var $DirectoryString;
142 var $ExtKeyUsageSyntax;
143 var $BasicConstraints;
145 var $CRLDistributionPoints;
146 var $AuthorityKeyIdentifier;
147 var $CertificatePolicies;
148 var $AuthorityInfoAccessSyntax;
150 var $SubjectDirectoryAttributes;
151 var $PrivateKeyUsagePeriod;
154 var $NameConstraints;
159 var $netscape_cert_type;
160 var $netscape_comment;
161 var $netscape_ca_policy_url;
164 var $RelativeDistinguishedName;
167 var $IssuingDistributionPoint;
169 var $CertificateIssuer;
170 var $HoldInstructionCode;
171 var $SignedPublicKeyAndChallenge;
175 * ASN.1 syntax for various DN attributes
183 * ASN.1 syntax for Certificate Signing Requests (RFC2986)
188 var $CertificationRequest;
191 * ASN.1 syntax for Certificate Revocation Lists (RFC5280)
196 var $CertificateList;
223 * Object identifiers for X.509 certificates
227 * @link http://en.wikipedia.org/wiki/Object_identifier
232 * The certificate authorities
240 * The currently loaded certificate
248 * The signature subject
250 * There's no guarantee \phpseclib\File\X509 is going to reencode an X.509 cert in the same way it was originally
251 * encoded so we take save the portion of the original cert that the signature would have made for.
256 var $signatureSubject;
259 * Certificate Start Date
267 * Certificate End Date
285 * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
286 * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
291 var $currentKeyIdentifier;
310 * Default Constructor.
312 * @return \phpseclib\File\X509
315 function __construct()
317 // Explicitly Tagged Module, 1988 Syntax
318 // http://tools.ietf.org/html/rfc5280#appendix-A.1
320 $this->DirectoryString = array(
321 'type' => ASN1::TYPE_CHOICE,
323 'teletexString' => array('type' => ASN1::TYPE_TELETEX_STRING),
324 'printableString' => array('type' => ASN1::TYPE_PRINTABLE_STRING),
325 'universalString' => array('type' => ASN1::TYPE_UNIVERSAL_STRING),
326 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING),
327 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING)
331 $this->PKCS9String = array(
332 'type' => ASN1::TYPE_CHOICE,
334 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING),
335 'directoryString' => $this->DirectoryString
339 $this->AttributeValue = array('type' => ASN1::TYPE_ANY);
341 $AttributeType = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
343 $AttributeTypeAndValue = array(
344 'type' => ASN1::TYPE_SEQUENCE,
346 'type' => $AttributeType,
347 'value'=> $this->AttributeValue
352 In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
353 but they can be useful at times when either there is no unique attribute in the entry or you
354 want to ensure that the entry's DN contains some useful identifying information.
356 - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
358 $this->RelativeDistinguishedName = array(
359 'type' => ASN1::TYPE_SET,
362 'children' => $AttributeTypeAndValue
365 // http://tools.ietf.org/html/rfc5280#section-4.1.2.4
366 $RDNSequence = array(
367 'type' => ASN1::TYPE_SEQUENCE,
368 // RDNSequence does not define a min or a max, which means it doesn't have one
371 'children' => $this->RelativeDistinguishedName
375 'type' => ASN1::TYPE_CHOICE,
377 'rdnSequence' => $RDNSequence
381 // http://tools.ietf.org/html/rfc5280#section-4.1.1.2
382 $AlgorithmIdentifier = array(
383 'type' => ASN1::TYPE_SEQUENCE,
385 'algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
386 'parameters' => array(
387 'type' => ASN1::TYPE_ANY,
394 A certificate using system MUST reject the certificate if it encounters
395 a critical extension it does not recognize; however, a non-critical
396 extension may be ignored if it is not recognized.
398 http://tools.ietf.org/html/rfc5280#section-4.2
401 'type' => ASN1::TYPE_SEQUENCE,
403 'extnId' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
405 'type' => ASN1::TYPE_BOOLEAN,
409 'extnValue' => array('type' => ASN1::TYPE_OCTET_STRING)
413 $this->Extensions = array(
414 'type' => ASN1::TYPE_SEQUENCE,
416 // technically, it's MAX, but we'll assume anything < 0 is MAX
418 // if 'children' isn't an array then 'min' and 'max' must be defined
419 'children' => $Extension
422 $SubjectPublicKeyInfo = array(
423 'type' => ASN1::TYPE_SEQUENCE,
425 'algorithm' => $AlgorithmIdentifier,
426 'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING)
430 $UniqueIdentifier = array('type' => ASN1::TYPE_BIT_STRING);
433 'type' => ASN1::TYPE_CHOICE,
435 'utcTime' => array('type' => ASN1::TYPE_UTC_TIME),
436 'generalTime' => array('type' => ASN1::TYPE_GENERALIZED_TIME)
440 // http://tools.ietf.org/html/rfc5280#section-4.1.2.5
442 'type' => ASN1::TYPE_SEQUENCE,
444 'notBefore' => $Time,
449 $CertificateSerialNumber = array('type' => ASN1::TYPE_INTEGER);
452 'type' => ASN1::TYPE_INTEGER,
453 'mapping' => array('v1', 'v2', 'v3')
456 // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
457 $TBSCertificate = array(
458 'type' => ASN1::TYPE_SEQUENCE,
460 // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
461 // reenforce that fact
468 'serialNumber' => $CertificateSerialNumber,
469 'signature' => $AlgorithmIdentifier,
470 'issuer' => $this->Name,
471 'validity' => $Validity,
472 'subject' => $this->Name,
473 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo,
474 // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
475 'issuerUniqueID' => array(
479 ) + $UniqueIdentifier,
480 'subjectUniqueID' => array(
484 ) + $UniqueIdentifier,
485 // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
486 // it's not IMPLICIT, it's EXPLICIT
487 'extensions' => array(
491 ) + $this->Extensions
495 $this->Certificate = array(
496 'type' => ASN1::TYPE_SEQUENCE,
498 'tbsCertificate' => $TBSCertificate,
499 'signatureAlgorithm' => $AlgorithmIdentifier,
500 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
504 $this->KeyUsage = array(
505 'type' => ASN1::TYPE_BIT_STRING,
519 $this->BasicConstraints = array(
520 'type' => ASN1::TYPE_SEQUENCE,
523 'type' => ASN1::TYPE_BOOLEAN,
527 'pathLenConstraint' => array(
528 'type' => ASN1::TYPE_INTEGER,
534 $this->KeyIdentifier = array('type' => ASN1::TYPE_OCTET_STRING);
536 $OrganizationalUnitNames = array(
537 'type' => ASN1::TYPE_SEQUENCE,
539 'max' => 4, // ub-organizational-units
540 'children' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
543 $PersonalName = array(
544 'type' => ASN1::TYPE_SET,
547 'type' => ASN1::TYPE_PRINTABLE_STRING,
552 'given-name' => array(
553 'type' => ASN1::TYPE_PRINTABLE_STRING,
559 'type' => ASN1::TYPE_PRINTABLE_STRING,
564 'generation-qualifier' => array(
565 'type' => ASN1::TYPE_PRINTABLE_STRING,
573 $NumericUserIdentifier = array('type' => ASN1::TYPE_NUMERIC_STRING);
575 $OrganizationName = array('type' => ASN1::TYPE_PRINTABLE_STRING);
577 $PrivateDomainName = array(
578 'type' => ASN1::TYPE_CHOICE,
580 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING),
581 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
585 $TerminalIdentifier = array('type' => ASN1::TYPE_PRINTABLE_STRING);
587 $NetworkAddress = array('type' => ASN1::TYPE_NUMERIC_STRING);
589 $AdministrationDomainName = array(
590 'type' => ASN1::TYPE_CHOICE,
591 // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or
592 // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC
593 'class' => ASN1::CLASS_APPLICATION,
596 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING),
597 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
601 $CountryName = array(
602 'type' => ASN1::TYPE_CHOICE,
603 // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or
604 // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC
605 'class' => ASN1::CLASS_APPLICATION,
608 'x121-dcc-code' => array('type' => ASN1::TYPE_NUMERIC_STRING),
609 'iso-3166-alpha2-code' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
613 $AnotherName = array(
614 'type' => ASN1::TYPE_SEQUENCE,
616 'type-id' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
618 'type' => ASN1::TYPE_ANY,
626 $ExtensionAttribute = array(
627 'type' => ASN1::TYPE_SEQUENCE,
629 'extension-attribute-type' => array(
630 'type' => ASN1::TYPE_PRINTABLE_STRING,
635 'extension-attribute-value' => array(
636 'type' => ASN1::TYPE_ANY,
644 $ExtensionAttributes = array(
645 'type' => ASN1::TYPE_SET,
647 'max' => 256, // ub-extension-attributes
648 'children' => $ExtensionAttribute
651 $BuiltInDomainDefinedAttribute = array(
652 'type' => ASN1::TYPE_SEQUENCE,
654 'type' => array('type' => ASN1::TYPE_PRINTABLE_STRING),
655 'value' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
659 $BuiltInDomainDefinedAttributes = array(
660 'type' => ASN1::TYPE_SEQUENCE,
662 'max' => 4, // ub-domain-defined-attributes
663 'children' => $BuiltInDomainDefinedAttribute
666 $BuiltInStandardAttributes = array(
667 'type' => ASN1::TYPE_SEQUENCE,
669 'country-name' => array('optional' => true) + $CountryName,
670 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName,
671 'network-address' => array(
676 'terminal-identifier' => array(
680 ) + $TerminalIdentifier,
681 'private-domain-name' => array(
685 ) + $PrivateDomainName,
686 'organization-name' => array(
690 ) + $OrganizationName,
691 'numeric-user-identifier' => array(
695 ) + $NumericUserIdentifier,
696 'personal-name' => array(
701 'organizational-unit-names' => array(
705 ) + $OrganizationalUnitNames
710 'type' => ASN1::TYPE_SEQUENCE,
712 'built-in-standard-attributes' => $BuiltInStandardAttributes,
713 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes,
714 'extension-attributes' => array('optional' => true) + $ExtensionAttributes
718 $EDIPartyName = array(
719 'type' => ASN1::TYPE_SEQUENCE,
721 'nameAssigner' => array(
725 ) + $this->DirectoryString,
726 // partyName is technically required but \phpseclib\File\ASN1 doesn't currently support non-optional constants and
727 // setting it to optional gets the job done in any event.
728 'partyName' => array(
732 ) + $this->DirectoryString
736 $GeneralName = array(
737 'type' => ASN1::TYPE_CHOICE,
739 'otherName' => array(
744 'rfc822Name' => array(
745 'type' => ASN1::TYPE_IA5_STRING,
751 'type' => ASN1::TYPE_IA5_STRING,
756 'x400Address' => array(
761 'directoryName' => array(
766 'ediPartyName' => array(
771 'uniformResourceIdentifier' => array(
772 'type' => ASN1::TYPE_IA5_STRING,
777 'iPAddress' => array(
778 'type' => ASN1::TYPE_OCTET_STRING,
783 'registeredID' => array(
784 'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
792 $GeneralNames = array(
793 'type' => ASN1::TYPE_SEQUENCE,
796 'children' => $GeneralName
799 $this->IssuerAltName = $GeneralNames;
801 $ReasonFlags = array(
802 'type' => ASN1::TYPE_BIT_STRING,
807 'affiliationChanged',
809 'cessationOfOperation',
811 'privilegeWithdrawn',
816 $DistributionPointName = array(
817 'type' => ASN1::TYPE_CHOICE,
824 'nameRelativeToCRLIssuer' => array(
828 ) + $this->RelativeDistinguishedName
832 $DistributionPoint = array(
833 'type' => ASN1::TYPE_SEQUENCE,
835 'distributionPoint' => array(
839 ) + $DistributionPointName,
845 'cRLIssuer' => array(
853 $this->CRLDistributionPoints = array(
854 'type' => ASN1::TYPE_SEQUENCE,
857 'children' => $DistributionPoint
860 $this->AuthorityKeyIdentifier = array(
861 'type' => ASN1::TYPE_SEQUENCE,
863 'keyIdentifier' => array(
867 ) + $this->KeyIdentifier,
868 'authorityCertIssuer' => array(
873 'authorityCertSerialNumber' => array(
877 ) + $CertificateSerialNumber
881 $PolicyQualifierId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
883 $PolicyQualifierInfo = array(
884 'type' => ASN1::TYPE_SEQUENCE,
886 'policyQualifierId' => $PolicyQualifierId,
887 'qualifier' => array('type' => ASN1::TYPE_ANY)
891 $CertPolicyId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
893 $PolicyInformation = array(
894 'type' => ASN1::TYPE_SEQUENCE,
896 'policyIdentifier' => $CertPolicyId,
897 'policyQualifiers' => array(
898 'type' => ASN1::TYPE_SEQUENCE,
902 'children' => $PolicyQualifierInfo
907 $this->CertificatePolicies = array(
908 'type' => ASN1::TYPE_SEQUENCE,
911 'children' => $PolicyInformation
914 $this->PolicyMappings = array(
915 'type' => ASN1::TYPE_SEQUENCE,
919 'type' => ASN1::TYPE_SEQUENCE,
921 'issuerDomainPolicy' => $CertPolicyId,
922 'subjectDomainPolicy' => $CertPolicyId
927 $KeyPurposeId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
929 $this->ExtKeyUsageSyntax = array(
930 'type' => ASN1::TYPE_SEQUENCE,
933 'children' => $KeyPurposeId
936 $AccessDescription = array(
937 'type' => ASN1::TYPE_SEQUENCE,
939 'accessMethod' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
940 'accessLocation' => $GeneralName
944 $this->AuthorityInfoAccessSyntax = array(
945 'type' => ASN1::TYPE_SEQUENCE,
948 'children' => $AccessDescription
951 $this->SubjectAltName = $GeneralNames;
953 $this->PrivateKeyUsagePeriod = array(
954 'type' => ASN1::TYPE_SEQUENCE,
956 'notBefore' => array(
960 'type' => ASN1::TYPE_GENERALIZED_TIME),
965 'type' => ASN1::TYPE_GENERALIZED_TIME)
969 $BaseDistance = array('type' => ASN1::TYPE_INTEGER);
971 $GeneralSubtree = array(
972 'type' => ASN1::TYPE_SEQUENCE,
974 'base' => $GeneralName,
979 'default' => new BigInteger(0)
989 $GeneralSubtrees = array(
990 'type' => ASN1::TYPE_SEQUENCE,
993 'children' => $GeneralSubtree
996 $this->NameConstraints = array(
997 'type' => ASN1::TYPE_SEQUENCE,
999 'permittedSubtrees' => array(
1003 ) + $GeneralSubtrees,
1004 'excludedSubtrees' => array(
1008 ) + $GeneralSubtrees
1012 $this->CPSuri = array('type' => ASN1::TYPE_IA5_STRING);
1014 $DisplayText = array(
1015 'type' => ASN1::TYPE_CHOICE,
1016 'children' => array(
1017 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING),
1018 'visibleString' => array('type' => ASN1::TYPE_VISIBLE_STRING),
1019 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING),
1020 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING)
1024 $NoticeReference = array(
1025 'type' => ASN1::TYPE_SEQUENCE,
1026 'children' => array(
1027 'organization' => $DisplayText,
1028 'noticeNumbers' => array(
1029 'type' => ASN1::TYPE_SEQUENCE,
1032 'children' => array('type' => ASN1::TYPE_INTEGER)
1037 $this->UserNotice = array(
1038 'type' => ASN1::TYPE_SEQUENCE,
1039 'children' => array(
1040 'noticeRef' => array(
1043 ) + $NoticeReference,
1044 'explicitText' => array(
1051 // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
1052 $this->netscape_cert_type = array(
1053 'type' => ASN1::TYPE_BIT_STRING,
1066 $this->netscape_comment = array('type' => ASN1::TYPE_IA5_STRING);
1067 $this->netscape_ca_policy_url = array('type' => ASN1::TYPE_IA5_STRING);
1069 // attribute is used in RFC2986 but we're using the RFC5280 definition
1072 'type' => ASN1::TYPE_SEQUENCE,
1073 'children' => array(
1074 'type' => $AttributeType,
1076 'type' => ASN1::TYPE_SET,
1079 'children' => $this->AttributeValue
1084 $this->SubjectDirectoryAttributes = array(
1085 'type' => ASN1::TYPE_SEQUENCE,
1088 'children' => $Attribute
1091 // adapted from <http://tools.ietf.org/html/rfc2986>
1093 $Attributes = array(
1094 'type' => ASN1::TYPE_SET,
1097 'children' => $Attribute
1100 $CertificationRequestInfo = array(
1101 'type' => ASN1::TYPE_SEQUENCE,
1102 'children' => array(
1104 'type' => ASN1::TYPE_INTEGER,
1105 'mapping' => array('v1')
1107 'subject' => $this->Name,
1108 'subjectPKInfo' => $SubjectPublicKeyInfo,
1109 'attributes' => array(
1117 $this->CertificationRequest = array(
1118 'type' => ASN1::TYPE_SEQUENCE,
1119 'children' => array(
1120 'certificationRequestInfo' => $CertificationRequestInfo,
1121 'signatureAlgorithm' => $AlgorithmIdentifier,
1122 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
1126 $RevokedCertificate = array(
1127 'type' => ASN1::TYPE_SEQUENCE,
1128 'children' => array(
1129 'userCertificate' => $CertificateSerialNumber,
1130 'revocationDate' => $Time,
1131 'crlEntryExtensions' => array(
1133 ) + $this->Extensions
1137 $TBSCertList = array(
1138 'type' => ASN1::TYPE_SEQUENCE,
1139 'children' => array(
1144 'signature' => $AlgorithmIdentifier,
1145 'issuer' => $this->Name,
1146 'thisUpdate' => $Time,
1147 'nextUpdate' => array(
1150 'revokedCertificates' => array(
1151 'type' => ASN1::TYPE_SEQUENCE,
1155 'children' => $RevokedCertificate
1157 'crlExtensions' => array(
1161 ) + $this->Extensions
1165 $this->CertificateList = array(
1166 'type' => ASN1::TYPE_SEQUENCE,
1167 'children' => array(
1168 'tbsCertList' => $TBSCertList,
1169 'signatureAlgorithm' => $AlgorithmIdentifier,
1170 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
1174 $this->CRLNumber = array('type' => ASN1::TYPE_INTEGER);
1176 $this->CRLReason = array('type' => ASN1::TYPE_ENUMERATED,
1181 'affiliationChanged',
1183 'cessationOfOperation',
1185 // Value 7 is not used.
1186 8 => 'removeFromCRL',
1187 'privilegeWithdrawn',
1192 $this->IssuingDistributionPoint = array('type' => ASN1::TYPE_SEQUENCE,
1193 'children' => array(
1194 'distributionPoint' => array(
1198 ) + $DistributionPointName,
1199 'onlyContainsUserCerts' => array(
1200 'type' => ASN1::TYPE_BOOLEAN,
1206 'onlyContainsCACerts' => array(
1207 'type' => ASN1::TYPE_BOOLEAN,
1213 'onlySomeReasons' => array(
1218 'indirectCRL' => array(
1219 'type' => ASN1::TYPE_BOOLEAN,
1225 'onlyContainsAttributeCerts' => array(
1226 'type' => ASN1::TYPE_BOOLEAN,
1235 $this->InvalidityDate = array('type' => ASN1::TYPE_GENERALIZED_TIME);
1237 $this->CertificateIssuer = $GeneralNames;
1239 $this->HoldInstructionCode = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
1241 $PublicKeyAndChallenge = array(
1242 'type' => ASN1::TYPE_SEQUENCE,
1243 'children' => array(
1244 'spki' => $SubjectPublicKeyInfo,
1245 'challenge' => array('type' => ASN1::TYPE_IA5_STRING)
1249 $this->SignedPublicKeyAndChallenge = array(
1250 'type' => ASN1::TYPE_SEQUENCE,
1251 'children' => array(
1252 'publicKeyAndChallenge' => $PublicKeyAndChallenge,
1253 'signatureAlgorithm' => $AlgorithmIdentifier,
1254 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
1258 $this->PostalAddress = array(
1259 'type' => ASN1::TYPE_SEQUENCE,
1263 'children' => $this->DirectoryString
1266 // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
1267 $this->oids = array(
1268 '1.3.6.1.5.5.7' => 'id-pkix',
1269 '1.3.6.1.5.5.7.1' => 'id-pe',
1270 '1.3.6.1.5.5.7.2' => 'id-qt',
1271 '1.3.6.1.5.5.7.3' => 'id-kp',
1272 '1.3.6.1.5.5.7.48' => 'id-ad',
1273 '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
1274 '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
1275 '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
1276 '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
1277 '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
1278 '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
1280 '2.5.4.41' => 'id-at-name',
1281 '2.5.4.4' => 'id-at-surname',
1282 '2.5.4.42' => 'id-at-givenName',
1283 '2.5.4.43' => 'id-at-initials',
1284 '2.5.4.44' => 'id-at-generationQualifier',
1285 '2.5.4.3' => 'id-at-commonName',
1286 '2.5.4.7' => 'id-at-localityName',
1287 '2.5.4.8' => 'id-at-stateOrProvinceName',
1288 '2.5.4.10' => 'id-at-organizationName',
1289 '2.5.4.11' => 'id-at-organizationalUnitName',
1290 '2.5.4.12' => 'id-at-title',
1291 '2.5.4.13' => 'id-at-description',
1292 '2.5.4.46' => 'id-at-dnQualifier',
1293 '2.5.4.6' => 'id-at-countryName',
1294 '2.5.4.5' => 'id-at-serialNumber',
1295 '2.5.4.65' => 'id-at-pseudonym',
1296 '2.5.4.17' => 'id-at-postalCode',
1297 '2.5.4.9' => 'id-at-streetAddress',
1298 '2.5.4.45' => 'id-at-uniqueIdentifier',
1299 '2.5.4.72' => 'id-at-role',
1300 '2.5.4.16' => 'id-at-postalAddress',
1302 '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
1303 '1.2.840.113549.1.9' => 'pkcs-9',
1304 '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress',
1305 '2.5.29' => 'id-ce',
1306 '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
1307 '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
1308 '2.5.29.15' => 'id-ce-keyUsage',
1309 '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
1310 '2.5.29.32' => 'id-ce-certificatePolicies',
1311 '2.5.29.32.0' => 'anyPolicy',
1313 '2.5.29.33' => 'id-ce-policyMappings',
1314 '2.5.29.17' => 'id-ce-subjectAltName',
1315 '2.5.29.18' => 'id-ce-issuerAltName',
1316 '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
1317 '2.5.29.19' => 'id-ce-basicConstraints',
1318 '2.5.29.30' => 'id-ce-nameConstraints',
1319 '2.5.29.36' => 'id-ce-policyConstraints',
1320 '2.5.29.31' => 'id-ce-cRLDistributionPoints',
1321 '2.5.29.37' => 'id-ce-extKeyUsage',
1322 '2.5.29.37.0' => 'anyExtendedKeyUsage',
1323 '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
1324 '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
1325 '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
1326 '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
1327 '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
1328 '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
1329 '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
1330 '2.5.29.46' => 'id-ce-freshestCRL',
1331 '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
1332 '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
1333 '2.5.29.20' => 'id-ce-cRLNumber',
1334 '2.5.29.28' => 'id-ce-issuingDistributionPoint',
1335 '2.5.29.27' => 'id-ce-deltaCRLIndicator',
1336 '2.5.29.21' => 'id-ce-cRLReasons',
1337 '2.5.29.29' => 'id-ce-certificateIssuer',
1338 '2.5.29.23' => 'id-ce-holdInstructionCode',
1339 '1.2.840.10040.2' => 'holdInstruction',
1340 '1.2.840.10040.2.1' => 'id-holdinstruction-none',
1341 '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
1342 '1.2.840.10040.2.3' => 'id-holdinstruction-reject',
1343 '2.5.29.24' => 'id-ce-invalidityDate',
1345 '1.2.840.113549.2.2' => 'md2',
1346 '1.2.840.113549.2.5' => 'md5',
1347 '1.3.14.3.2.26' => 'id-sha1',
1348 '1.2.840.10040.4.1' => 'id-dsa',
1349 '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
1350 '1.2.840.113549.1.1' => 'pkcs-1',
1351 '1.2.840.113549.1.1.1' => 'rsaEncryption',
1352 '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
1353 '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
1354 '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
1355 '1.2.840.10046.2.1' => 'dhpublicnumber',
1356 '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
1357 '1.2.840.10045' => 'ansi-X9-62',
1358 '1.2.840.10045.4' => 'id-ecSigType',
1359 '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
1360 '1.2.840.10045.1' => 'id-fieldType',
1361 '1.2.840.10045.1.1' => 'prime-field',
1362 '1.2.840.10045.1.2' => 'characteristic-two-field',
1363 '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
1364 '1.2.840.10045.1.2.3.1' => 'gnBasis',
1365 '1.2.840.10045.1.2.3.2' => 'tpBasis',
1366 '1.2.840.10045.1.2.3.3' => 'ppBasis',
1367 '1.2.840.10045.2' => 'id-publicKeyType',
1368 '1.2.840.10045.2.1' => 'id-ecPublicKey',
1369 '1.2.840.10045.3' => 'ellipticCurve',
1370 '1.2.840.10045.3.0' => 'c-TwoCurve',
1371 '1.2.840.10045.3.0.1' => 'c2pnb163v1',
1372 '1.2.840.10045.3.0.2' => 'c2pnb163v2',
1373 '1.2.840.10045.3.0.3' => 'c2pnb163v3',
1374 '1.2.840.10045.3.0.4' => 'c2pnb176w1',
1375 '1.2.840.10045.3.0.5' => 'c2pnb191v1',
1376 '1.2.840.10045.3.0.6' => 'c2pnb191v2',
1377 '1.2.840.10045.3.0.7' => 'c2pnb191v3',
1378 '1.2.840.10045.3.0.8' => 'c2pnb191v4',
1379 '1.2.840.10045.3.0.9' => 'c2pnb191v5',
1380 '1.2.840.10045.3.0.10' => 'c2pnb208w1',
1381 '1.2.840.10045.3.0.11' => 'c2pnb239v1',
1382 '1.2.840.10045.3.0.12' => 'c2pnb239v2',
1383 '1.2.840.10045.3.0.13' => 'c2pnb239v3',
1384 '1.2.840.10045.3.0.14' => 'c2pnb239v4',
1385 '1.2.840.10045.3.0.15' => 'c2pnb239v5',
1386 '1.2.840.10045.3.0.16' => 'c2pnb272w1',
1387 '1.2.840.10045.3.0.17' => 'c2pnb304w1',
1388 '1.2.840.10045.3.0.18' => 'c2pnb359v1',
1389 '1.2.840.10045.3.0.19' => 'c2pnb368w1',
1390 '1.2.840.10045.3.0.20' => 'c2pnb431r1',
1391 '1.2.840.10045.3.1' => 'primeCurve',
1392 '1.2.840.10045.3.1.1' => 'prime192v1',
1393 '1.2.840.10045.3.1.2' => 'prime192v2',
1394 '1.2.840.10045.3.1.3' => 'prime192v3',
1395 '1.2.840.10045.3.1.4' => 'prime239v1',
1396 '1.2.840.10045.3.1.5' => 'prime239v2',
1397 '1.2.840.10045.3.1.6' => 'prime239v3',
1398 '1.2.840.10045.3.1.7' => 'prime256v1',
1399 '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
1400 '1.2.840.113549.1.1.9' => 'id-pSpecified',
1401 '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
1402 '1.2.840.113549.1.1.8' => 'id-mgf1',
1403 '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
1404 '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
1405 '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
1406 '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
1407 '2.16.840.1.101.3.4.2.4' => 'id-sha224',
1408 '2.16.840.1.101.3.4.2.1' => 'id-sha256',
1409 '2.16.840.1.101.3.4.2.2' => 'id-sha384',
1410 '2.16.840.1.101.3.4.2.3' => 'id-sha512',
1411 '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
1412 '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
1413 '1.2.643.2.2.20' => 'id-GostR3410-2001',
1414 '1.2.643.2.2.19' => 'id-GostR3410-94',
1415 // Netscape Object Identifiers from "Netscape Certificate Extensions"
1416 '2.16.840.1.113730' => 'netscape',
1417 '2.16.840.1.113730.1' => 'netscape-cert-extension',
1418 '2.16.840.1.113730.1.1' => 'netscape-cert-type',
1419 '2.16.840.1.113730.1.13' => 'netscape-comment',
1420 '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
1421 // the following are X.509 extensions not supported by phpseclib
1422 '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
1423 '1.2.840.113533.7.65.0' => 'entrustVersInfo',
1424 '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
1425 // for Certificate Signing Requests
1426 // see http://tools.ietf.org/html/rfc2985
1427 '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name
1428 '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations
1429 '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request
1434 * Load X.509 certificate
1436 * Returns an associative array describing the X.509 cert or a false if the cert failed to load
1438 * @param string $cert
1443 function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT)
1445 if (is_array($cert) && isset($cert['tbsCertificate'])) {
1446 unset($this->currentCert);
1447 unset($this->currentKeyIdentifier);
1448 $this->dn = $cert['tbsCertificate']['subject'];
1449 if (!isset($this->dn)) {
1452 $this->currentCert = $cert;
1454 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
1455 $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
1457 unset($this->signatureSubject);
1464 if ($mode != self::FORMAT_DER) {
1465 $newcert = $this->_extractBER($cert);
1466 if ($mode == self::FORMAT_PEM && $cert == $newcert) {
1472 if ($cert === false) {
1473 $this->currentCert = false;
1477 $asn1->loadOIDs($this->oids);
1478 $decoded = $asn1->decodeBER($cert);
1480 if (!empty($decoded)) {
1481 $x509 = $asn1->asn1map($decoded[0], $this->Certificate);
1483 if (!isset($x509) || $x509 === false) {
1484 $this->currentCert = false;
1488 $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
1490 $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
1491 $this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1);
1492 $this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1);
1494 $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
1495 $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
1497 $this->currentCert = $x509;
1498 $this->dn = $x509['tbsCertificate']['subject'];
1500 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
1501 $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
1507 * Save X.509 certificate
1509 * @param array $cert
1510 * @param int $format optional
1514 function saveX509($cert, $format = self::FORMAT_PEM)
1516 if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
1521 // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
1522 case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
1523 case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
1526 switch ($algorithm) {
1527 case 'rsaEncryption':
1528 $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
1529 = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
1530 /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier."
1531 -- https://tools.ietf.org/html/rfc3279#section-2.3.1
1533 given that and the fact that RSA keys appear ot be the only key type for which the parameters field can be blank,
1534 it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever.
1536 $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null;
1537 // https://tools.ietf.org/html/rfc3279#section-2.2.1
1538 $cert['signatureAlgorithm']['parameters'] = null;
1539 $cert['tbsCertificate']['signature']['parameters'] = null;
1544 $asn1->loadOIDs($this->oids);
1547 $type_utf8_string = array('type' => ASN1::TYPE_UTF8_STRING);
1548 $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
1549 $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
1550 $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
1551 $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string;
1552 $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string;
1553 $filters['signatureAlgorithm']['parameters'] = $type_utf8_string;
1554 $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
1555 //$filters['policyQualifiers']['qualifier'] = $type_utf8_string;
1556 $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
1557 $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;
1559 /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib\File\ASN1::TYPE_IA5_STRING.
1560 \phpseclib\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
1563 $filters['policyQualifiers']['qualifier']
1564 = array('type' => ASN1::TYPE_IA5_STRING);
1566 $asn1->loadFilters($filters);
1568 $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1);
1569 $this->_mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence', $asn1);
1570 $this->_mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence', $asn1);
1572 $cert = $asn1->encodeDER($cert, $this->Certificate);
1575 case self::FORMAT_DER:
1577 // case self::FORMAT_PEM:
1579 return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(Base64::encode($cert), 64) . '-----END CERTIFICATE-----';
1584 * Map extension values from octet string to extension-specific internal
1587 * @param array ref $root
1588 * @param string $path
1589 * @param object $asn1
1592 function _mapInExtensions(&$root, $path, $asn1)
1594 $extensions = &$this->_subArray($root, $path);
1596 if (is_array($extensions)) {
1597 for ($i = 0; $i < count($extensions); $i++) {
1598 $id = $extensions[$i]['extnId'];
1599 $value = &$extensions[$i]['extnValue'];
1600 $value = Base64::decode($value);
1601 $decoded = $asn1->decodeBER($value);
1602 /* [extnValue] contains the DER encoding of an ASN.1 value
1603 corresponding to the extension type identified by extnID */
1604 $map = $this->_getMapping($id);
1605 if (!is_bool($map)) {
1606 $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
1607 $value = $mapped === false ? $decoded[0] : $mapped;
1609 if ($id == 'id-ce-certificatePolicies') {
1610 for ($j = 0; $j < count($value); $j++) {
1611 if (!isset($value[$j]['policyQualifiers'])) {
1614 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
1615 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1616 $map = $this->_getMapping($subid);
1617 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1618 if ($map !== false) {
1619 $decoded = $asn1->decodeBER($subvalue);
1620 $mapped = $asn1->asn1map($decoded[0], $map);
1621 $subvalue = $mapped === false ? $decoded[0] : $mapped;
1627 $value = Base64::encode($value);
1634 * Map extension values from extension-specific internal format to
1637 * @param array ref $root
1638 * @param string $path
1639 * @param object $asn1
1642 function _mapOutExtensions(&$root, $path, $asn1)
1644 $extensions = &$this->_subArray($root, $path);
1646 if (is_array($extensions)) {
1647 $size = count($extensions);
1648 for ($i = 0; $i < $size; $i++) {
1649 if ($extensions[$i] instanceof Element) {
1653 $id = $extensions[$i]['extnId'];
1654 $value = &$extensions[$i]['extnValue'];
1657 case 'id-ce-certificatePolicies':
1658 for ($j = 0; $j < count($value); $j++) {
1659 if (!isset($value[$j]['policyQualifiers'])) {
1662 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
1663 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1664 $map = $this->_getMapping($subid);
1665 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1666 if ($map !== false) {
1667 // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's
1668 // actual type is \phpseclib\File\ASN1::TYPE_ANY
1669 $subvalue = new Element($asn1->encodeDER($subvalue, $map));
1674 case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
1675 if (isset($value['authorityCertSerialNumber'])) {
1676 if ($value['authorityCertSerialNumber']->toBytes() == '') {
1677 $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
1678 $value['authorityCertSerialNumber'] = new Element($temp);
1683 /* [extnValue] contains the DER encoding of an ASN.1 value
1684 corresponding to the extension type identified by extnID */
1685 $map = $this->_getMapping($id);
1686 if (is_bool($map)) {
1688 //user_error($id . ' is not a currently supported extension');
1689 unset($extensions[$i]);
1692 $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
1693 $value = Base64::encode($temp);
1700 * Map attribute values from ANY type to attribute-specific internal
1703 * @param array ref $root
1704 * @param string $path
1705 * @param object $asn1
1708 function _mapInAttributes(&$root, $path, $asn1)
1710 $attributes = &$this->_subArray($root, $path);
1712 if (is_array($attributes)) {
1713 for ($i = 0; $i < count($attributes); $i++) {
1714 $id = $attributes[$i]['type'];
1715 /* $value contains the DER encoding of an ASN.1 value
1716 corresponding to the attribute type identified by type */
1717 $map = $this->_getMapping($id);
1718 if (is_array($attributes[$i]['value'])) {
1719 $values = &$attributes[$i]['value'];
1720 for ($j = 0; $j < count($values); $j++) {
1721 $value = $asn1->encodeDER($values[$j], $this->AttributeValue);
1722 $decoded = $asn1->decodeBER($value);
1723 if (!is_bool($map)) {
1724 $mapped = $asn1->asn1map($decoded[0], $map);
1725 if ($mapped !== false) {
1726 $values[$j] = $mapped;
1728 if ($id == 'pkcs-9-at-extensionRequest') {
1729 $this->_mapInExtensions($values, $j, $asn1);
1732 $values[$j] = Base64::encode($value);
1741 * Map attribute values from attribute-specific internal format to
1744 * @param array ref $root
1745 * @param string $path
1746 * @param object $asn1
1749 function _mapOutAttributes(&$root, $path, $asn1)
1751 $attributes = &$this->_subArray($root, $path);
1753 if (is_array($attributes)) {
1754 $size = count($attributes);
1755 for ($i = 0; $i < $size; $i++) {
1756 /* [value] contains the DER encoding of an ASN.1 value
1757 corresponding to the attribute type identified by type */
1758 $id = $attributes[$i]['type'];
1759 $map = $this->_getMapping($id);
1760 if ($map === false) {
1761 //user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
1762 unset($attributes[$i]);
1763 } elseif (is_array($attributes[$i]['value'])) {
1764 $values = &$attributes[$i]['value'];
1765 for ($j = 0; $j < count($values); $j++) {
1767 case 'pkcs-9-at-extensionRequest':
1768 $this->_mapOutExtensions($values, $j, $asn1);
1772 if (!is_bool($map)) {
1773 $temp = $asn1->encodeDER($values[$j], $map);
1774 $decoded = $asn1->decodeBER($temp);
1775 $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue);
1784 * Map DN values from ANY type to DN-specific internal
1787 * @param array ref $root
1788 * @param string $path
1789 * @param object $asn1
1792 function _mapInDNs(&$root, $path, $asn1)
1794 $dns = &$this->_subArray($root, $path);
1796 if (is_array($dns)) {
1797 for ($i = 0; $i < count($dns); $i++) {
1798 for ($j = 0; $j < count($dns[$i]); $j++) {
1799 $type = $dns[$i][$j]['type'];
1800 $value = &$dns[$i][$j]['value'];
1801 if (is_object($value) && $value instanceof Element) {
1802 $map = $this->_getMapping($type);
1803 if (!is_bool($map)) {
1804 $decoded = $asn1->decodeBER($value);
1805 $value = $asn1->asn1map($decoded[0], $map);
1814 * Map DN values from DN-specific internal format to
1817 * @param array ref $root
1818 * @param string $path
1819 * @param object $asn1
1822 function _mapOutDNs(&$root, $path, $asn1)
1824 $dns = &$this->_subArray($root, $path);
1826 if (is_array($dns)) {
1827 $size = count($dns);
1828 for ($i = 0; $i < $size; $i++) {
1829 for ($j = 0; $j < count($dns[$i]); $j++) {
1830 $type = $dns[$i][$j]['type'];
1831 $value = &$dns[$i][$j]['value'];
1832 if (is_object($value) && $value instanceof Element) {
1836 $map = $this->_getMapping($type);
1837 if (!is_bool($map)) {
1838 $value = new Element($asn1->encodeDER($value, $map));
1846 * Associate an extension ID to an extension mapping
1848 * @param string $extnId
1852 function _getMapping($extnId)
1854 if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object
1859 case 'id-ce-keyUsage':
1860 return $this->KeyUsage;
1861 case 'id-ce-basicConstraints':
1862 return $this->BasicConstraints;
1863 case 'id-ce-subjectKeyIdentifier':
1864 return $this->KeyIdentifier;
1865 case 'id-ce-cRLDistributionPoints':
1866 return $this->CRLDistributionPoints;
1867 case 'id-ce-authorityKeyIdentifier':
1868 return $this->AuthorityKeyIdentifier;
1869 case 'id-ce-certificatePolicies':
1870 return $this->CertificatePolicies;
1871 case 'id-ce-extKeyUsage':
1872 return $this->ExtKeyUsageSyntax;
1873 case 'id-pe-authorityInfoAccess':
1874 return $this->AuthorityInfoAccessSyntax;
1875 case 'id-ce-subjectAltName':
1876 return $this->SubjectAltName;
1877 case 'id-ce-subjectDirectoryAttributes':
1878 return $this->SubjectDirectoryAttributes;
1879 case 'id-ce-privateKeyUsagePeriod':
1880 return $this->PrivateKeyUsagePeriod;
1881 case 'id-ce-issuerAltName':
1882 return $this->IssuerAltName;
1883 case 'id-ce-policyMappings':
1884 return $this->PolicyMappings;
1885 case 'id-ce-nameConstraints':
1886 return $this->NameConstraints;
1888 case 'netscape-cert-type':
1889 return $this->netscape_cert_type;
1890 case 'netscape-comment':
1891 return $this->netscape_comment;
1892 case 'netscape-ca-policy-url':
1893 return $this->netscape_ca_policy_url;
1895 // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
1896 // back around to asn1map() and we don't want it decoded again.
1898 // return $this->CPSuri;
1899 case 'id-qt-unotice':
1900 return $this->UserNotice;
1902 // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
1903 case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
1904 case 'entrustVersInfo':
1905 // http://support.microsoft.com/kb/287547
1906 case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
1907 case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
1908 // "SET Secure Electronic Transaction Specification"
1909 // http://www.maithean.com/docs/set_bk3.pdf
1910 case '2.23.42.7.0': // id-set-hashedRootKey
1914 case 'pkcs-9-at-unstructuredName':
1915 return $this->PKCS9String;
1916 case 'pkcs-9-at-challengePassword':
1917 return $this->DirectoryString;
1918 case 'pkcs-9-at-extensionRequest':
1919 return $this->Extensions;
1922 case 'id-ce-cRLNumber':
1923 return $this->CRLNumber;
1924 case 'id-ce-deltaCRLIndicator':
1925 return $this->CRLNumber;
1926 case 'id-ce-issuingDistributionPoint':
1927 return $this->IssuingDistributionPoint;
1928 case 'id-ce-freshestCRL':
1929 return $this->CRLDistributionPoints;
1930 case 'id-ce-cRLReasons':
1931 return $this->CRLReason;
1932 case 'id-ce-invalidityDate':
1933 return $this->InvalidityDate;
1934 case 'id-ce-certificateIssuer':
1935 return $this->CertificateIssuer;
1936 case 'id-ce-holdInstructionCode':
1937 return $this->HoldInstructionCode;
1938 case 'id-at-postalAddress':
1939 return $this->PostalAddress;
1946 * Load an X.509 certificate as a certificate authority
1948 * @param string $cert
1952 function loadCA($cert)
1955 $oldcert = $this->currentCert;
1956 $oldsigsubj = $this->signatureSubject;
1957 $oldkeyid = $this->currentKeyIdentifier;
1959 $cert = $this->loadX509($cert);
1962 $this->currentCert = $oldcert;
1963 $this->signatureSubject = $oldsigsubj;
1964 $this->currentKeyIdentifier = $oldkeyid;
1969 /* From RFC5280 "PKIX Certificate and CRL Profile":
1971 If the keyUsage extension is present, then the subject public key
1972 MUST NOT be used to verify signatures on certificates or CRLs unless
1973 the corresponding keyCertSign or cRLSign bit is set. */
1974 //$keyUsage = $this->getExtension('id-ce-keyUsage');
1975 //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
1979 /* From RFC5280 "PKIX Certificate and CRL Profile":
1981 The cA boolean indicates whether the certified public key may be used
1982 to verify certificate signatures. If the cA boolean is not asserted,
1983 then the keyCertSign bit in the key usage extension MUST NOT be
1984 asserted. If the basic constraints extension is not present in a
1985 version 3 certificate, or the extension is present but the cA boolean
1986 is not asserted, then the certified public key MUST NOT be used to
1987 verify certificate signatures. */
1988 //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
1989 //if (!$basicConstraints || !$basicConstraints['cA']) {
1993 $this->CAs[] = $cert;
1996 $this->currentCert = $oldcert;
1997 $this->signatureSubject = $oldsigsubj;
2003 * Validate an X.509 certificate against a URL
2005 * From RFC2818 "HTTP over TLS":
2007 * Matching is performed using the matching rules specified by
2008 * [RFC2459]. If more than one identity of a given type is present in
2009 * the certificate (e.g., more than one dNSName name, a match in any one
2010 * of the set is considered acceptable.) Names may contain the wildcard
2011 * character * which is considered to match any single domain name
2012 * component or component fragment. E.g., *.a.com matches foo.a.com but
2013 * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
2015 * @param string $url
2019 function validateURL($url)
2021 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2025 $components = parse_url($url);
2026 if (!isset($components['host'])) {
2030 if ($names = $this->getExtension('id-ce-subjectAltName')) {
2031 foreach ($names as $key => $value) {
2032 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
2035 /* From RFC2818 "HTTP over TLS":
2037 If a subjectAltName extension of type dNSName is present, that MUST
2038 be used as the identity. Otherwise, the (most specific) Common Name
2039 field in the Subject field of the certificate MUST be used. Although
2040 the use of the Common Name is existing practice, it is deprecated and
2041 Certification Authorities are encouraged to use the dNSName instead. */
2042 if (preg_match('#^' . $value . '$#', $components['host'])) {
2047 /* From RFC2818 "HTTP over TLS":
2049 In some cases, the URI is specified as an IP address rather than a
2050 hostname. In this case, the iPAddress subjectAltName must be present
2051 in the certificate and must exactly match the IP in the URI. */
2052 if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
2060 if ($value = $this->getDNProp('id-at-commonName')) {
2061 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
2062 return preg_match('#^' . $value . '$#', $components['host']);
2071 * If $date isn't defined it is assumed to be the current date.
2073 * @param int $date optional
2076 function validateDate($date = null)
2078 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2082 if (!isset($date)) {
2086 $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
2087 $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];
2089 $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
2090 $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
2093 case $date < @strtotime($notBefore):
2094 case $date > @strtotime($notAfter):
2102 * Validate a signature
2104 * Works on X.509 certs, CSR's and CRL's.
2105 * Returns true if the signature is verified, false if it is not correct or null on error
2107 * By default returns false for self-signed certs. Call validateSignature(false) to make this support
2110 * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
2112 * @param bool $caonly optional
2116 function validateSignature($caonly = true)
2118 if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
2123 "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
2124 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
2126 implement pathLenConstraint in the id-ce-basicConstraints extension */
2129 case isset($this->currentCert['tbsCertificate']):
2132 case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']:
2133 case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING):
2134 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2135 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
2137 case !is_array($authorityKey):
2138 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2139 $signingCert = $this->currentCert; // working cert
2143 if (!empty($this->CAs)) {
2144 for ($i = 0; $i < count($this->CAs); $i++) {
2145 // even if the cert is a self-signed one we still want to see if it's a CA;
2146 // if not, we'll conditionally return an error
2147 $ca = $this->CAs[$i];
2149 case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']:
2150 case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
2151 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2152 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2154 case !is_array($authorityKey):
2155 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2156 $signingCert = $ca; // working cert
2161 if (count($this->CAs) == $i && $caonly) {
2164 } elseif (!isset($signingCert) || $caonly) {
2167 return $this->_validateSignature(
2168 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
2169 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
2170 $this->currentCert['signatureAlgorithm']['algorithm'],
2171 substr(Base64::decode($this->currentCert['signature']), 1),
2172 $this->signatureSubject
2174 case isset($this->currentCert['certificationRequestInfo']):
2175 return $this->_validateSignature(
2176 $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
2177 $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
2178 $this->currentCert['signatureAlgorithm']['algorithm'],
2179 substr(Base64::decode($this->currentCert['signature']), 1),
2180 $this->signatureSubject
2182 case isset($this->currentCert['publicKeyAndChallenge']):
2183 return $this->_validateSignature(
2184 $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
2185 $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
2186 $this->currentCert['signatureAlgorithm']['algorithm'],
2187 substr(Base64::decode($this->currentCert['signature']), 1),
2188 $this->signatureSubject
2190 case isset($this->currentCert['tbsCertList']):
2191 if (!empty($this->CAs)) {
2192 for ($i = 0; $i < count($this->CAs); $i++) {
2193 $ca = $this->CAs[$i];
2195 case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']:
2196 case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
2197 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2198 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2200 case !is_array($authorityKey):
2201 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2202 $signingCert = $ca; // working cert
2208 if (!isset($signingCert)) {
2211 return $this->_validateSignature(
2212 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
2213 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
2214 $this->currentCert['signatureAlgorithm']['algorithm'],
2215 substr(Base64::decode($this->currentCert['signature']), 1),
2216 $this->signatureSubject
2224 * Validates a signature
2226 * Returns true if the signature is verified and false if it is not correct.
2227 * If the algorithms are unsupposed an exception is thrown.
2229 * @param string $publicKeyAlgorithm
2230 * @param string $publicKey
2231 * @param string $signatureAlgorithm
2232 * @param string $signature
2233 * @param string $signatureSubject
2235 * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
2238 function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
2240 switch ($publicKeyAlgorithm) {
2241 case 'rsaEncryption':
2243 $rsa->load($publicKey);
2245 switch ($signatureAlgorithm) {
2246 case 'md2WithRSAEncryption':
2247 case 'md5WithRSAEncryption':
2248 case 'sha1WithRSAEncryption':
2249 case 'sha224WithRSAEncryption':
2250 case 'sha256WithRSAEncryption':
2251 case 'sha384WithRSAEncryption':
2252 case 'sha512WithRSAEncryption':
2253 $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
2254 if (!@$rsa->verify($signatureSubject, $signature, RSA::PADDING_PKCS1)) {
2259 throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
2263 throw new UnsupportedAlgorithmException('Public key algorithm unsupported');
2270 * Reformat public keys
2272 * Reformats a public key to a format supported by phpseclib (if applicable)
2274 * @param string $algorithm
2275 * @param string $key
2279 function _reformatKey($algorithm, $key)
2281 switch ($algorithm) {
2282 case 'rsaEncryption':
2284 "-----BEGIN RSA PUBLIC KEY-----\r\n" .
2285 // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits
2286 // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox
2287 // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
2288 chunk_split(Base64::encode(substr(Base64::decode($key), 1)), 64) .
2289 '-----END RSA PUBLIC KEY-----';
2296 * Decodes an IP address
2298 * Takes in a base64 encoded "blob" and returns a human readable IP address
2304 function _decodeIP($ip)
2306 return inet_ntop(Base64::decode($ip));
2310 * Encodes an IP address
2312 * Takes a human readable IP address into a base64-encoded "blob"
2318 function _encodeIP($ip)
2320 return Base64::encode(inet_pton($ip));
2324 * "Normalizes" a Distinguished Name property
2326 * @param string $propName
2330 function _translateDNProp($propName)
2332 switch (strtolower($propName)) {
2333 case 'id-at-countryname':
2336 return 'id-at-countryName';
2337 case 'id-at-organizationname':
2338 case 'organizationname':
2340 return 'id-at-organizationName';
2341 case 'id-at-dnqualifier':
2343 return 'id-at-dnQualifier';
2344 case 'id-at-commonname':
2347 return 'id-at-commonName';
2348 case 'id-at-stateorprovincename':
2349 case 'stateorprovincename':
2352 case 'provincename':
2354 return 'id-at-stateOrProvinceName';
2355 case 'id-at-localityname':
2356 case 'localityname':
2358 return 'id-at-localityName';
2359 case 'id-emailaddress':
2360 case 'emailaddress':
2361 return 'pkcs-9-at-emailAddress';
2362 case 'id-at-serialnumber':
2363 case 'serialnumber':
2364 return 'id-at-serialNumber';
2365 case 'id-at-postalcode':
2367 return 'id-at-postalCode';
2368 case 'id-at-streetaddress':
2369 case 'streetaddress':
2370 return 'id-at-streetAddress';
2373 return 'id-at-name';
2374 case 'id-at-givenname':
2376 return 'id-at-givenName';
2377 case 'id-at-surname':
2380 return 'id-at-surname';
2381 case 'id-at-initials':
2383 return 'id-at-initials';
2384 case 'id-at-generationqualifier':
2385 case 'generationqualifier':
2386 return 'id-at-generationQualifier';
2387 case 'id-at-organizationalunitname':
2388 case 'organizationalunitname':
2390 return 'id-at-organizationalUnitName';
2391 case 'id-at-pseudonym':
2393 return 'id-at-pseudonym';
2396 return 'id-at-title';
2397 case 'id-at-description':
2399 return 'id-at-description';
2402 return 'id-at-role';
2403 case 'id-at-uniqueidentifier':
2404 case 'uniqueidentifier':
2405 case 'x500uniqueidentifier':
2406 return 'id-at-uniqueIdentifier';
2407 case 'postaladdress':
2408 case 'id-at-postaladdress':
2409 return 'id-at-postalAddress';
2416 * Set a Distinguished Name property
2418 * @param string $propName
2419 * @param mixed $propValue
2420 * @param string $type optional
2424 function setDNProp($propName, $propValue, $type = 'utf8String')
2426 if (empty($this->dn)) {
2427 $this->dn = array('rdnSequence' => array());
2430 if (($propName = $this->_translateDNProp($propName)) === false) {
2434 foreach ((array) $propValue as $v) {
2435 if (!is_array($v) && isset($type)) {
2436 $v = array($type => $v);
2438 $this->dn['rdnSequence'][] = array(
2440 'type' => $propName,
2450 * Remove Distinguished Name properties
2452 * @param string $propName
2455 function removeDNProp($propName)
2457 if (empty($this->dn)) {
2461 if (($propName = $this->_translateDNProp($propName)) === false) {
2465 $dn = &$this->dn['rdnSequence'];
2467 for ($i = 0; $i < $size; $i++) {
2468 if ($dn[$i][0]['type'] == $propName) {
2473 $dn = array_values($dn);
2477 * Get Distinguished Name properties
2479 * @param string $propName
2480 * @param array $dn optional
2481 * @param bool $withType optional
2485 function getDNProp($propName, $dn = null, $withType = false)
2495 if (($propName = $this->_translateDNProp($propName)) === false) {
2500 $asn1->loadOIDs($this->oids);
2502 $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2503 $asn1->loadFilters($filters);
2504 $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2505 $dn = $dn['rdnSequence'];
2507 for ($i = 0; $i < count($dn); $i++) {
2508 if ($dn[$i][0]['type'] == $propName) {
2509 $v = $dn[$i][0]['value'];
2512 foreach ($v as $type => $s) {
2513 $type = array_search($type, $asn1->ANYmap, true);
2514 if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2515 $s = $asn1->convert($s, $type);
2523 $v = array_pop($v); // Always strip data type.
2525 } elseif (is_object($v) && $v instanceof Element) {
2526 $map = $this->_getMapping($propName);
2527 if (!is_bool($map)) {
2528 $decoded = $asn1->decodeBER($v);
2529 $v = $asn1->asn1map($decoded[0], $map);
2541 * Set a Distinguished Name
2544 * @param bool $merge optional
2545 * @param string $type optional
2549 function setDN($dn, $merge = false, $type = 'utf8String')
2555 if (is_array($dn)) {
2556 if (isset($dn['rdnSequence'])) {
2557 $this->dn = $dn; // No merge here.
2561 // handles stuff generated by openssl_x509_parse()
2562 foreach ($dn as $prop => $value) {
2563 if (!$this->setDNProp($prop, $value, $type)) {
2570 // handles everything else
2571 $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
2572 for ($i = 1; $i < count($results); $i+=2) {
2573 $prop = trim($results[$i], ', =/');
2574 $value = $results[$i + 1];
2575 if (!$this->setDNProp($prop, $value, $type)) {
2584 * Get the Distinguished Name for a certificates subject
2586 * @param mixed $format optional
2587 * @param array $dn optional
2591 function getDN($format = self::DN_ARRAY, $dn = null)
2594 $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
2597 switch ((int) $format) {
2598 case self::DN_ARRAY:
2602 $asn1->loadOIDs($this->oids);
2604 $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2605 $asn1->loadFilters($filters);
2606 $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2607 return $asn1->encodeDER($dn, $this->Name);
2608 case self::DN_CANON:
2609 // No SEQUENCE around RDNs and all string values normalized as
2610 // trimmed lowercase UTF-8 with all spacing as one blank.
2611 // constructed RDNs will not be canonicalized
2613 $asn1->loadOIDs($this->oids);
2615 $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2616 $asn1->loadFilters($filters);
2618 $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2619 foreach ($dn['rdnSequence'] as $rdn) {
2620 foreach ($rdn as $i => $attr) {
2622 if (is_array($attr['value'])) {
2623 foreach ($attr['value'] as $type => $v) {
2624 $type = array_search($type, $asn1->ANYmap, true);
2625 if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2626 $v = $asn1->convert($v, $type);
2628 $v = preg_replace('/\s+/', ' ', $v);
2629 $attr['value'] = strtolower(trim($v));
2636 $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName);
2640 $dn = $this->getDN(self::DN_CANON, $dn);
2641 $hash = new Hash('sha1');
2642 $hash = $hash->hash($dn);
2643 extract(unpack('Vhash', $hash));
2644 return strtolower(Hex::encode(pack('N', $hash)));
2647 // Default is to return a string.
2653 $asn1->loadOIDs($this->oids);
2655 $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2656 $asn1->loadFilters($filters);
2657 $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2659 foreach ($dn['rdnSequence'] as $field) {
2660 $prop = $field[0]['type'];
2661 $value = $field[0]['value'];
2665 case 'id-at-countryName':
2668 case 'id-at-stateOrProvinceName':
2671 case 'id-at-organizationName':
2674 case 'id-at-organizationalUnitName':
2677 case 'id-at-commonName':
2680 case 'id-at-localityName':
2683 case 'id-at-surname':
2686 case 'id-at-uniqueIdentifier':
2688 $desc = 'x500UniqueIdentifier';
2690 case 'id-at-postalAddress':
2692 $desc = 'postalAddress';
2696 $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop);
2702 if (is_array($value)) {
2703 foreach ($value as $type => $v) {
2704 $type = array_search($type, $asn1->ANYmap, true);
2705 if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2706 $v = $asn1->convert($v, $type);
2713 if (is_array($value)) {
2714 $value = array_pop($value); // Always strip data type.
2716 } elseif (is_object($value) && $value instanceof Element) {
2717 $callback = create_function('$x', 'return "\x" . bin2hex($x[0]);');
2718 $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
2720 $output.= $desc . '=' . $value;
2721 $result[$desc] = isset($result[$desc]) ?
2722 array_merge((array) $dn[$prop], array($value)) :
2727 return $format == self::DN_OPENSSL ? $result : $output;
2731 * Get the Distinguished Name for a certificate/crl issuer
2733 * @param int $format optional
2737 function getIssuerDN($format = self::DN_ARRAY)
2740 case !isset($this->currentCert) || !is_array($this->currentCert):
2742 case isset($this->currentCert['tbsCertificate']):
2743 return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
2744 case isset($this->currentCert['tbsCertList']):
2745 return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
2752 * Get the Distinguished Name for a certificate/csr subject
2755 * @param int $format optional
2759 function getSubjectDN($format = self::DN_ARRAY)
2762 case !empty($this->dn):
2763 return $this->getDN($format);
2764 case !isset($this->currentCert) || !is_array($this->currentCert):
2766 case isset($this->currentCert['tbsCertificate']):
2767 return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
2768 case isset($this->currentCert['certificationRequestInfo']):
2769 return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
2776 * Get an individual Distinguished Name property for a certificate/crl issuer
2778 * @param string $propName
2779 * @param bool $withType optional
2783 function getIssuerDNProp($propName, $withType = false)
2786 case !isset($this->currentCert) || !is_array($this->currentCert):
2788 case isset($this->currentCert['tbsCertificate']):
2789 return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
2790 case isset($this->currentCert['tbsCertList']):
2791 return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
2798 * Get an individual Distinguished Name property for a certificate/csr subject
2800 * @param string $propName
2801 * @param bool $withType optional
2805 function getSubjectDNProp($propName, $withType = false)
2808 case !empty($this->dn):
2809 return $this->getDNProp($propName, null, $withType);
2810 case !isset($this->currentCert) || !is_array($this->currentCert):
2812 case isset($this->currentCert['tbsCertificate']):
2813 return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
2814 case isset($this->currentCert['certificationRequestInfo']):
2815 return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
2822 * Get the certificate chain for the current cert
2829 $chain = array($this->currentCert);
2831 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2834 if (empty($this->CAs)) {
2838 $currentCert = $chain[count($chain) - 1];
2839 for ($i = 0; $i < count($this->CAs); $i++) {
2840 $ca = $this->CAs[$i];
2841 if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
2842 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
2843 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2845 case !is_array($authorityKey):
2846 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2847 if ($currentCert === $ca) {
2855 if ($i == count($this->CAs)) {
2859 foreach ($chain as $key => $value) {
2860 $chain[$key] = new X509();
2861 $chain[$key]->loadX509($value);
2869 * Key needs to be a \phpseclib\Crypt\RSA object
2871 * @param object $key
2875 function setPublicKey($key)
2877 $key->setPublicKey();
2878 $this->publicKey = $key;
2884 * Key needs to be a \phpseclib\Crypt\RSA object
2886 * @param object $key
2889 function setPrivateKey($key)
2891 $this->privateKey = $key;
2897 * Used for SPKAC CSR's
2899 * @param string $challenge
2902 function setChallenge($challenge)
2904 $this->challenge = $challenge;
2908 * Gets the public key
2910 * Returns a \phpseclib\Crypt\RSA object or a false.
2915 function getPublicKey()
2917 if (isset($this->publicKey)) {
2918 return $this->publicKey;
2921 if (isset($this->currentCert) && is_array($this->currentCert)) {
2922 foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
2923 $keyinfo = $this->_subArray($this->currentCert, $path);
2924 if (!empty($keyinfo)) {
2929 if (empty($keyinfo)) {
2933 $key = $keyinfo['subjectPublicKey'];
2935 switch ($keyinfo['algorithm']['algorithm']) {
2936 case 'rsaEncryption':
2937 $publicKey = new RSA();
2938 $publicKey->load($key);
2939 $publicKey->setPublicKey();
2949 * Load a Certificate Signing Request
2951 * @param string $csr
2955 function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
2957 if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
2958 unset($this->currentCert);
2959 unset($this->currentKeyIdentifier);
2960 unset($this->signatureSubject);
2961 $this->dn = $csr['certificationRequestInfo']['subject'];
2962 if (!isset($this->dn)) {
2966 $this->currentCert = $csr;
2970 // see http://tools.ietf.org/html/rfc2986
2974 if ($mode != self::FORMAT_DER) {
2975 $newcsr = $this->_extractBER($csr);
2976 if ($mode == self::FORMAT_PEM && $csr == $newcsr) {
2983 if ($csr === false) {
2984 $this->currentCert = false;
2988 $asn1->loadOIDs($this->oids);
2989 $decoded = $asn1->decodeBER($csr);
2991 if (empty($decoded)) {
2992 $this->currentCert = false;
2996 $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
2997 if (!isset($csr) || $csr === false) {
2998 $this->currentCert = false;
3002 $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
3003 $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
3005 $this->dn = $csr['certificationRequestInfo']['subject'];
3007 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3009 $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
3010 $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
3011 $key = $this->_reformatKey($algorithm, $key);
3013 switch ($algorithm) {
3014 case 'rsaEncryption':
3015 $this->publicKey = new RSA();
3016 $this->publicKey->load($key);
3017 $this->publicKey->setPublicKey();
3020 $this->publicKey = null;
3023 $this->currentKeyIdentifier = null;
3024 $this->currentCert = $csr;
3033 * @param int $format optional
3037 function saveCSR($csr, $format = self::FORMAT_PEM)
3039 if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
3044 case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
3045 case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
3048 switch ($algorithm) {
3049 case 'rsaEncryption':
3050 $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
3051 = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
3057 $asn1->loadOIDs($this->oids);
3060 $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
3061 = array('type' => ASN1::TYPE_UTF8_STRING);
3063 $asn1->loadFilters($filters);
3065 $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
3066 $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
3067 $csr = $asn1->encodeDER($csr, $this->CertificationRequest);
3070 case self::FORMAT_DER:
3072 // case self::FORMAT_PEM:
3074 return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(Base64::encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
3081 * SPKAC's are produced by the HTML5 keygen element:
3083 * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
3085 * @param string $csr
3089 function loadSPKAC($spkac)
3091 if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
3092 unset($this->currentCert);
3093 unset($this->currentKeyIdentifier);
3094 unset($this->signatureSubject);
3095 $this->currentCert = $spkac;
3099 // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
3103 // OpenSSL produces SPKAC's that are preceeded by the string SPKAC=
3104 $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
3105 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false;
3106 if ($temp != false) {
3111 if ($spkac === false) {
3112 $this->currentCert = false;
3116 $asn1->loadOIDs($this->oids);
3117 $decoded = $asn1->decodeBER($spkac);
3119 if (empty($decoded)) {
3120 $this->currentCert = false;
3124 $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge);
3126 if (!isset($spkac) || $spkac === false) {
3127 $this->currentCert = false;
3131 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3133 $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
3134 $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
3135 $key = $this->_reformatKey($algorithm, $key);
3137 switch ($algorithm) {
3138 case 'rsaEncryption':
3139 $this->publicKey = new RSA();
3140 $this->publicKey->load($key);
3141 $this->publicKey->setPublicKey();
3144 $this->publicKey = null;
3147 $this->currentKeyIdentifier = null;
3148 $this->currentCert = $spkac;
3154 * Save a SPKAC CSR request
3157 * @param int $format optional
3161 function saveSPKAC($spkac, $format = self::FORMAT_PEM)
3163 if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
3167 $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
3170 case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']):
3173 switch ($algorithm) {
3174 case 'rsaEncryption':
3175 $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
3176 = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
3182 $asn1->loadOIDs($this->oids);
3183 $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);
3186 case self::FORMAT_DER:
3188 // case self::FORMAT_PEM:
3190 // OpenSSL's implementation of SPKAC requires the SPKAC be preceeded by SPKAC= and since there are pretty much
3191 // no other SPKAC decoders phpseclib will use that same format
3192 return 'SPKAC=' . Base64::encode($spkac);
3197 * Load a Certificate Revocation List
3199 * @param string $crl
3203 function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
3205 if (is_array($crl) && isset($crl['tbsCertList'])) {
3206 $this->currentCert = $crl;
3207 unset($this->signatureSubject);
3213 if ($mode != self::FORMAT_DER) {
3214 $newcrl = $this->_extractBER($crl);
3215 if ($mode == self::FORMAT_PEM && $crl == $newcrl) {
3222 if ($crl === false) {
3223 $this->currentCert = false;
3227 $asn1->loadOIDs($this->oids);
3228 $decoded = $asn1->decodeBER($crl);
3230 if (empty($decoded)) {
3231 $this->currentCert = false;
3235 $crl = $asn1->asn1map($decoded[0], $this->CertificateList);
3236 if (!isset($crl) || $crl === false) {
3237 $this->currentCert = false;
3241 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3243 $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
3244 $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
3245 $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
3246 if (is_array($rclist)) {
3247 foreach ($rclist as $i => $extension) {
3248 $this->_mapInExtensions($rclist, "$i/crlEntryExtensions", $asn1);
3252 $this->currentKeyIdentifier = null;
3253 $this->currentCert = $crl;
3259 * Save Certificate Revocation List.
3262 * @param int $format optional
3266 function saveCRL($crl, $format = self::FORMAT_PEM)
3268 if (!is_array($crl) || !isset($crl['tbsCertList'])) {
3274 $asn1->loadOIDs($this->oids);
3277 $filters['tbsCertList']['issuer']['rdnSequence']['value']
3278 = array('type' => ASN1::TYPE_UTF8_STRING);
3279 $filters['tbsCertList']['signature']['parameters']
3280 = array('type' => ASN1::TYPE_UTF8_STRING);
3281 $filters['signatureAlgorithm']['parameters']
3282 = array('type' => ASN1::TYPE_UTF8_STRING);
3284 if (empty($crl['tbsCertList']['signature']['parameters'])) {
3285 $filters['tbsCertList']['signature']['parameters']
3286 = array('type' => ASN1::TYPE_NULL);
3289 if (empty($crl['signatureAlgorithm']['parameters'])) {
3290 $filters['signatureAlgorithm']['parameters']
3291 = array('type' => ASN1::TYPE_NULL);
3294 $asn1->loadFilters($filters);
3296 $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
3297 $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
3298 $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
3299 if (is_array($rclist)) {
3300 foreach ($rclist as $i => $extension) {
3301 $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
3305 $crl = $asn1->encodeDER($crl, $this->CertificateList);
3308 case self::FORMAT_DER:
3310 // case self::FORMAT_PEM:
3312 return "-----BEGIN X509 CRL-----\r\n" . chunk_split(Base64::encode($crl), 64) . '-----END X509 CRL-----';
3317 * Helper function to build a time field according to RFC 3280 section
3318 * - 4.1.2.5 Validity
3319 * - 5.1.2.4 This Update
3320 * - 5.1.2.5 Next Update
3321 * - 5.1.2.6 Revoked Certificates
3322 * by choosing utcTime iff year of date given is before 2050 and generalTime else.
3324 * @param string $date in format date('D, d M Y H:i:s O')
3328 function _timeField($date)
3330 $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
3332 return array('utcTime' => $date);
3334 return array('generalTime' => $date);
3339 * Sign an X.509 certificate
3341 * $issuer's private key needs to be loaded.
3342 * $subject can be either an existing X.509 cert (if you want to resign it),
3343 * a CSR or something with the DN and public key explicitly set.
3345 * @param \phpseclib\File\X509 $issuer
3346 * @param \phpseclib\File\X509 $subject
3347 * @param string $signatureAlgorithm optional
3351 function sign($issuer, $subject, $signatureAlgorithm = 'sha256WithRSAEncryption')
3353 if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
3357 if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
3361 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3362 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3364 if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
3365 $this->currentCert = $subject->currentCert;
3366 $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
3367 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3369 if (!empty($this->startDate)) {
3370 $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
3372 if (!empty($this->endDate)) {
3373 $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
3375 if (!empty($this->serialNumber)) {
3376 $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
3378 if (!empty($subject->dn)) {
3379 $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
3381 if (!empty($subject->publicKey)) {
3382 $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
3384 $this->removeExtension('id-ce-authorityKeyIdentifier');
3385 if (isset($subject->domains)) {
3386 $this->removeExtension('id-ce-subjectAltName');
3388 } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
3391 if (!isset($subject->publicKey)) {
3395 $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
3396 $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year'));
3397 /* "The serial number MUST be a positive integer"
3398 "Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
3399 -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2
3401 for the integer to be positive the leading bit needs to be 0 hence the
3402 application of a bitmap
3404 $serialNumber = !empty($this->serialNumber) ?
3405 $this->serialNumber :
3406 new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256);
3408 $this->currentCert = array(
3412 'serialNumber' => $serialNumber, // $this->setserialNumber()
3413 'signature' => array('algorithm' => $signatureAlgorithm),
3414 'issuer' => false, // this is going to be overwritten later
3415 'validity' => array(
3416 'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
3417 'notAfter' => $this->_timeField($endDate) // $this->setEndDate()
3419 'subject' => $subject->dn,
3420 'subjectPublicKeyInfo' => $subjectPublicKey
3422 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3423 'signature' => false // this is going to be overwritten later
3426 // Copy extensions from CSR.
3427 $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
3429 if (!empty($csrexts)) {
3430 $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
3434 $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
3436 if (isset($issuer->currentKeyIdentifier)) {
3437 $this->setExtension('id-ce-authorityKeyIdentifier', array(
3438 //'authorityCertIssuer' => array(
3440 // 'directoryName' => $issuer->dn
3443 'keyIdentifier' => $issuer->currentKeyIdentifier
3445 //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
3446 //if (isset($issuer->serialNumber)) {
3447 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3449 //unset($extensions);
3452 if (isset($subject->currentKeyIdentifier)) {
3453 $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
3458 if (isset($subject->domains) && count($subject->domains) > 1) {
3459 $altName = array_map(array('X509', '_dnsName'), $subject->domains);
3462 if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
3463 // should an IP address appear as the CN if no domain name is specified? idk
3464 //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
3465 $ipAddresses = array();
3466 foreach ($subject->ipAddresses as $ipAddress) {
3467 $encoded = $subject->_ipAddress($ipAddress);
3468 if ($encoded !== false) {
3469 $ipAddresses[] = $encoded;
3472 if (count($ipAddresses)) {
3473 $altName = array_merge($altName, $ipAddresses);
3477 if (!empty($altName)) {
3478 $this->setExtension('id-ce-subjectAltName', $altName);
3481 if ($this->caFlag) {
3482 $keyUsage = $this->getExtension('id-ce-keyUsage');
3484 $keyUsage = array();
3487 $this->setExtension(
3489 array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
3492 $basicConstraints = $this->getExtension('id-ce-basicConstraints');
3493 if (!$basicConstraints) {
3494 $basicConstraints = array();
3497 $this->setExtension(
3498 'id-ce-basicConstraints',
3499 array_unique(array_merge(array('cA' => true), $basicConstraints)),
3503 if (!isset($subject->currentKeyIdentifier)) {
3504 $this->setExtension('id-ce-subjectKeyIdentifier', Base64::encode($this->computeKeyIdentifier($this->currentCert)), false, false);
3508 // resync $this->signatureSubject
3509 // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it
3510 $tbsCertificate = $this->currentCert['tbsCertificate'];
3511 $this->loadX509($this->saveX509($this->currentCert));
3513 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
3514 $result['tbsCertificate'] = $tbsCertificate;
3516 $this->currentCert = $currentCert;
3517 $this->signatureSubject = $signatureSubject;
3528 function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
3530 if (!is_object($this->privateKey) || empty($this->dn)) {
3534 $origPublicKey = $this->publicKey;
3535 $class = get_class($this->privateKey);
3536 $this->publicKey = new $class();
3537 $this->publicKey->load($this->privateKey->getPublicKey());
3538 $this->publicKey->setPublicKey();
3539 if (!($publicKey = $this->_formatSubjectPublicKey())) {
3542 $this->publicKey = $origPublicKey;
3544 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3545 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3547 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
3548 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3549 if (!empty($this->dn)) {
3550 $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
3552 $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
3554 $this->currentCert = array(
3555 'certificationRequestInfo' =>
3558 'subject' => $this->dn,
3559 'subjectPKInfo' => $publicKey
3561 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3562 'signature' => false // this is going to be overwritten later
3566 // resync $this->signatureSubject
3567 // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it
3568 $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
3569 $this->loadCSR($this->saveCSR($this->currentCert));
3571 $result = $this->_sign($this->privateKey, $signatureAlgorithm);
3572 $result['certificationRequestInfo'] = $certificationRequestInfo;
3574 $this->currentCert = $currentCert;
3575 $this->signatureSubject = $signatureSubject;
3586 function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
3588 if (!is_object($this->privateKey)) {
3592 $origPublicKey = $this->publicKey;
3593 $class = get_class($this->privateKey);
3594 $this->publicKey = new $class();
3595 $this->publicKey->load($this->privateKey->getPublicKey());
3596 $this->publicKey->setPublicKey();
3597 $publicKey = $this->_formatSubjectPublicKey();
3601 $this->publicKey = $origPublicKey;
3603 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3604 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3606 // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
3607 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
3608 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3609 $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
3610 if (!empty($this->challenge)) {
3611 // the bitwise AND ensures that the output is a valid IA5String
3612 $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
3615 $this->currentCert = array(
3616 'publicKeyAndChallenge' =>
3618 'spki' => $publicKey,
3619 // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
3620 // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
3621 // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
3622 // we could alternatively do this instead if we ignored the specs:
3623 // Random::string(8) & str_repeat("\x7F", 8)
3624 'challenge' => !empty($this->challenge) ? $this->challenge : ''
3626 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3627 'signature' => false // this is going to be overwritten later
3631 // resync $this->signatureSubject
3632 // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it
3633 $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
3634 $this->loadSPKAC($this->saveSPKAC($this->currentCert));
3636 $result = $this->_sign($this->privateKey, $signatureAlgorithm);
3637 $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
3639 $this->currentCert = $currentCert;
3640 $this->signatureSubject = $signatureSubject;
3648 * $issuer's private key needs to be loaded.
3650 * @param \phpseclib\File\X509 $issuer
3651 * @param \phpseclib\File\X509 $crl
3652 * @param string $signatureAlgorithm optional
3656 function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
3658 if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
3662 $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3663 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
3664 $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
3666 if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
3667 $this->currentCert = $crl->currentCert;
3668 $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
3669 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3671 $this->currentCert = array(
3675 'signature' => array('algorithm' => $signatureAlgorithm),
3676 'issuer' => false, // this is going to be overwritten later
3677 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
3679 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3680 'signature' => false // this is going to be overwritten later
3684 $tbsCertList = &$this->currentCert['tbsCertList'];
3685 $tbsCertList['issuer'] = $issuer->dn;
3686 $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);
3688 if (!empty($this->endDate)) {
3689 $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
3691 unset($tbsCertList['nextUpdate']);
3694 if (!empty($this->serialNumber)) {
3695 $crlNumber = $this->serialNumber;
3697 $crlNumber = $this->getExtension('id-ce-cRLNumber');
3698 // "The CRL number is a non-critical CRL extension that conveys a
3699 // monotonically increasing sequence number for a given CRL scope and
3700 // CRL issuer. This extension allows users to easily determine when a
3701 // particular CRL supersedes another CRL."
3702 // -- https://tools.ietf.org/html/rfc5280#section-5.2.3
3703 $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null;
3706 $this->removeExtension('id-ce-authorityKeyIdentifier');
3707 $this->removeExtension('id-ce-issuerAltName');
3709 // Be sure version >= v2 if some extension found.
3710 $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
3712 if (!empty($tbsCertList['crlExtensions'])) {
3713 $version = 1; // v2.
3714 } elseif (!empty($tbsCertList['revokedCertificates'])) {
3715 foreach ($tbsCertList['revokedCertificates'] as $cert) {
3716 if (!empty($cert['crlEntryExtensions'])) {
3717 $version = 1; // v2.
3723 $tbsCertList['version'] = $version;
3727 // Store additional extensions.
3728 if (!empty($tbsCertList['version'])) { // At least v2.
3729 if (!empty($crlNumber)) {
3730 $this->setExtension('id-ce-cRLNumber', $crlNumber);
3733 if (isset($issuer->currentKeyIdentifier)) {
3734 $this->setExtension('id-ce-authorityKeyIdentifier', array(
3735 //'authorityCertIssuer' => array(
3737 // 'directoryName' => $issuer->dn
3740 'keyIdentifier' => $issuer->currentKeyIdentifier
3742 //$extensions = &$tbsCertList['crlExtensions'];
3743 //if (isset($issuer->serialNumber)) {
3744 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3746 //unset($extensions);
3749 $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);
3751 if ($issuerAltName !== false) {
3752 $this->setExtension('id-ce-issuerAltName', $issuerAltName);
3756 if (empty($tbsCertList['revokedCertificates'])) {
3757 unset($tbsCertList['revokedCertificates']);
3760 unset($tbsCertList);
3762 // resync $this->signatureSubject
3763 // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it
3764 $tbsCertList = $this->currentCert['tbsCertList'];
3765 $this->loadCRL($this->saveCRL($this->currentCert));
3767 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
3768 $result['tbsCertList'] = $tbsCertList;
3770 $this->currentCert = $currentCert;
3771 $this->signatureSubject = $signatureSubject;
3777 * X.509 certificate signing helper function.
3779 * @param object $key
3780 * @param \phpseclib\File\X509 $subject
3781 * @param string $signatureAlgorithm
3783 * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
3786 function _sign($key, $signatureAlgorithm)
3788 if ($key instanceof RSA) {
3789 switch ($signatureAlgorithm) {
3790 case 'md2WithRSAEncryption':
3791 case 'md5WithRSAEncryption':
3792 case 'sha1WithRSAEncryption':
3793 case 'sha224WithRSAEncryption':
3794 case 'sha256WithRSAEncryption':
3795 case 'sha384WithRSAEncryption':
3796 case 'sha512WithRSAEncryption':
3797 $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
3799 $this->currentCert['signature'] = Base64::encode("\0" . $key->sign($this->signatureSubject, RSA::PADDING_PKCS1));
3800 return $this->currentCert;
3802 throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
3806 throw new UnsupportedAlgorithmException('Unsupported public key algorithm');
3810 * Set certificate start date
3812 * @param string $date
3815 function setStartDate($date)
3817 $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
3821 * Set certificate end date
3823 * @param string $date
3826 function setEndDate($date)
3829 To indicate that a certificate has no well-defined expiration date,
3830 the notAfter SHOULD be assigned the GeneralizedTime value of
3833 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
3835 if (strtolower($date) == 'lifetime') {
3836 $temp = '99991231235959Z';
3838 $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
3839 $this->endDate = new Element($temp);
3841 $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
3848 * @param string $serial
3849 * @param $base optional
3852 function setSerialNumber($serial, $base = -256)
3854 $this->serialNumber = new BigInteger($serial, $base);
3858 * Turns the certificate into a certificate authority
3864 $this->caFlag = true;
3868 * Get a reference to a subarray
3870 * @param array $root
3871 * @param string $path absolute path with / as component separator
3872 * @param bool $create optional
3874 * @return array|false
3876 function &_subArray(&$root, $path, $create = false)
3880 if (!is_array($root)) {
3884 foreach (explode('/', $path) as $i) {
3885 if (!is_array($root)) {
3889 if (!isset($root[$i])) {
3894 $root[$i] = array();
3904 * Get a reference to an extension subarray
3906 * @param array $root
3907 * @param string $path optional absolute path with / as component separator
3908 * @param bool $create optional
3910 * @return array|false
3912 function &_extensions(&$root, $path = null, $create = false)
3914 if (!isset($root)) {
3915 $root = $this->currentCert;
3920 case !is_array($root):
3922 case isset($root['tbsCertificate']):
3923 $path = 'tbsCertificate/extensions';
3925 case isset($root['tbsCertList']):
3926 $path = 'tbsCertList/crlExtensions';
3928 case isset($root['certificationRequestInfo']):
3929 $pth = 'certificationRequestInfo/attributes';
3930 $attributes = &$this->_subArray($root, $pth, $create);
3932 if (is_array($attributes)) {
3933 foreach ($attributes as $key => $value) {
3934 if ($value['type'] == 'pkcs-9-at-extensionRequest') {
3935 $path = "$pth/$key/value/0";
3940 $key = count($attributes);
3941 $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array());
3942 $path = "$pth/$key/value/0";
3948 $extensions = &$this->_subArray($root, $path, $create);
3950 if (!is_array($extensions)) {
3959 * Remove an Extension
3962 * @param string $path optional
3966 function _removeExtension($id, $path = null)
3968 $extensions = &$this->_extensions($this->currentCert, $path);
3970 if (!is_array($extensions)) {
3975 foreach ($extensions as $key => $value) {
3976 if ($value['extnId'] == $id) {
3977 unset($extensions[$key]);
3982 $extensions = array_values($extensions);
3989 * Returns the extension if it exists and false if not
3992 * @param array $cert optional
3993 * @param string $path optional
3997 function _getExtension($id, $cert = null, $path = null)
3999 $extensions = $this->_extensions($cert, $path);
4001 if (!is_array($extensions)) {
4005 foreach ($extensions as $key => $value) {
4006 if ($value['extnId'] == $id) {
4007 return $value['extnValue'];
4015 * Returns a list of all extensions in use
4017 * @param array $cert optional
4018 * @param string $path optional
4022 function _getExtensions($cert = null, $path = null)
4024 $exts = $this->_extensions($cert, $path);
4025 $extensions = array();
4027 if (is_array($exts)) {
4028 foreach ($exts as $extension) {
4029 $extensions[] = $extension['extnId'];
4040 * @param mixed $value
4041 * @param bool $critical optional
4042 * @param bool $replace optional
4043 * @param string $path optional
4047 function _setExtension($id, $value, $critical = false, $replace = true, $path = null)
4049 $extensions = &$this->_extensions($this->currentCert, $path, true);
4051 if (!is_array($extensions)) {
4055 $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value);
4057 foreach ($extensions as $key => $value) {
4058 if ($value['extnId'] == $id) {
4063 $extensions[$key] = $newext;
4068 $extensions[] = $newext;
4073 * Remove a certificate, CSR or CRL Extension
4079 function removeExtension($id)
4081 return $this->_removeExtension($id);
4085 * Get a certificate, CSR or CRL Extension
4087 * Returns the extension if it exists and false if not
4090 * @param array $cert optional
4094 function getExtension($id, $cert = null)
4096 return $this->_getExtension($id, $cert);
4100 * Returns a list of all extensions in use in certificate, CSR or CRL
4102 * @param array $cert optional
4106 function getExtensions($cert = null)
4108 return $this->_getExtensions($cert);
4112 * Set a certificate, CSR or CRL Extension
4115 * @param mixed $value
4116 * @param bool $critical optional
4117 * @param bool $replace optional
4121 function setExtension($id, $value, $critical = false, $replace = true)
4123 return $this->_setExtension($id, $value, $critical, $replace);
4127 * Remove a CSR attribute.
4130 * @param int $disposition optional
4134 function removeAttribute($id, $disposition = self::ATTR_ALL)
4136 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes');
4138 if (!is_array($attributes)) {
4143 foreach ($attributes as $key => $attribute) {
4144 if ($attribute['type'] == $id) {
4145 $n = count($attribute['value']);
4147 case $disposition == self::ATTR_APPEND:
4148 case $disposition == self::ATTR_REPLACE:
4150 case $disposition >= $n:
4153 case $disposition == self::ATTR_ALL:
4155 unset($attributes[$key]);
4159 unset($attributes[$key]['value'][$disposition]);
4160 $attributes[$key]['value'] = array_values($attributes[$key]['value']);
4164 if ($result && $disposition != self::ATTR_ALL) {
4170 $attributes = array_values($attributes);
4175 * Get a CSR attribute
4177 * Returns the attribute if it exists and false if not
4180 * @param int $disposition optional
4181 * @param array $csr optional
4185 function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
4188 $csr = $this->currentCert;
4191 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
4193 if (!is_array($attributes)) {
4197 foreach ($attributes as $key => $attribute) {
4198 if ($attribute['type'] == $id) {
4199 $n = count($attribute['value']);
4201 case $disposition == self::ATTR_APPEND:
4202 case $disposition == self::ATTR_REPLACE:
4204 case $disposition == self::ATTR_ALL:
4205 return $attribute['value'];
4206 case $disposition >= $n:
4210 return $attribute['value'][$disposition];
4219 * Returns a list of all CSR attributes in use
4221 * @param array $csr optional
4225 function getAttributes($csr = null)
4228 $csr = $this->currentCert;
4231 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
4234 if (is_array($attributes)) {
4235 foreach ($attributes as $attribute) {
4236 $attrs[] = $attribute['type'];
4244 * Set a CSR attribute
4247 * @param mixed $value
4248 * @param bool $disposition optional
4252 function setAttribute($id, $value, $disposition = self::ATTR_ALL)
4254 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
4256 if (!is_array($attributes)) {
4260 switch ($disposition) {
4261 case self::ATTR_REPLACE:
4262 $disposition = self::ATTR_APPEND;
4263 case self::ATTR_ALL:
4264 $this->removeAttribute($id);
4268 foreach ($attributes as $key => $attribute) {
4269 if ($attribute['type'] == $id) {
4270 $n = count($attribute['value']);
4272 case $disposition == self::ATTR_APPEND:
4275 case $disposition >= $n:
4279 $attributes[$key]['value'][$disposition] = $value;
4286 case $disposition >= 0:
4289 $attributes[$last]['value'][] = $value;
4292 $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value));
4300 * Sets the subject key identifier
4302 * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
4304 * @param string $value
4307 function setKeyIdentifier($value)
4309 if (empty($value)) {
4310 unset($this->currentKeyIdentifier);
4312 $this->currentKeyIdentifier = Base64::encode($value);
4317 * Compute a public key identifier.
4319 * Although key identifiers may be set to any unique value, this function
4320 * computes key identifiers from public key according to the two
4321 * recommended methods (4.2.1.2 RFC 3280).
4322 * Highly polymorphic: try to accept all possible forms of key:
4324 * - \phpseclib\File\X509 object with public or private key defined
4325 * - Certificate or CSR array
4326 * - \phpseclib\File\ASN1\Element object
4327 * - PEM or DER string
4329 * @param mixed $key optional
4330 * @param int $method optional
4332 * @return string binary key identifier
4334 function computeKeyIdentifier($key = null, $method = 1)
4336 if (is_null($key)) {
4341 case is_string($key):
4343 case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
4344 return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
4345 case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
4346 return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
4347 case !is_object($key):
4349 case $key instanceof Element:
4350 // Assume the element is a bitstring-packed key.
4352 $decoded = $asn1->decodeBER($key->element);
4353 if (empty($decoded)) {
4356 $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING));
4360 $raw = Base64::decode($raw);
4361 // If the key is private, compute identifier from its corresponding public key.
4363 if (!$key->load($raw)) {
4364 return false; // Not an unencrypted RSA key.
4366 if ($key->getPrivateKey() !== false) { // If private.
4367 return $this->computeKeyIdentifier($key, $method);
4369 $key = $raw; // Is a public key.
4371 case $key instanceof X509:
4372 if (isset($key->publicKey)) {
4373 return $this->computeKeyIdentifier($key->publicKey, $method);
4375 if (isset($key->privateKey)) {
4376 return $this->computeKeyIdentifier($key->privateKey, $method);
4378 if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
4379 return $this->computeKeyIdentifier($key->currentCert, $method);
4382 default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA).
4383 $key = $key->getPublicKey('PKCS1');
4387 // If in PEM format, convert to binary.
4388 $key = $this->_extractBER($key);
4390 // Now we have the key string: compute its sha-1 sum.
4391 $hash = new Hash('sha1');
4392 $hash = $hash->hash($key);
4395 $hash = substr($hash, -8);
4396 $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
4403 * Format a public key as appropriate
4408 function _formatSubjectPublicKey()
4410 if ($this->publicKey instanceof RSA) {
4411 // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
4412 // the former is a good example of how to do fuzzing on the public key
4413 //return new Element(Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())));
4415 'algorithm' => array('algorithm' => 'rsaEncryption'),
4416 'subjectPublicKey' => $this->publicKey->getPublicKey('PKCS1')
4424 * Set the domain name's which the cert is to be valid for
4429 function setDomain()
4431 $this->domains = func_get_args();
4432 $this->removeDNProp('id-at-commonName');
4433 $this->setDNProp('id-at-commonName', $this->domains[0]);
4437 * Set the IP Addresses's which the cert is to be valid for
4440 * @param string $ipAddress optional
4442 function setIPAddress()
4444 $this->ipAddresses = func_get_args();
4446 if (!isset($this->domains)) {
4447 $this->removeDNProp('id-at-commonName');
4448 $this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
4454 * Helper function to build domain array
4457 * @param string $domain
4460 function _dnsName($domain)
4462 return array('dNSName' => $domain);
4466 * Helper function to build IP Address array
4468 * (IPv6 is not currently supported)
4471 * @param string $address
4474 function _iPAddress($address)
4476 return array('iPAddress' => $address);
4480 * Get the index of a revoked certificate.
4482 * @param array $rclist
4483 * @param string $serial
4484 * @param bool $create optional
4488 function _revokedCertificate(&$rclist, $serial, $create = false)
4490 $serial = new BigInteger($serial);
4492 foreach ($rclist as $i => $rc) {
4493 if (!($serial->compare($rc['userCertificate']))) {
4502 $i = count($rclist);
4503 $rclist[] = array('userCertificate' => $serial,
4504 'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O')));
4509 * Revoke a certificate.
4511 * @param string $serial
4512 * @param string $date optional
4516 function revoke($serial, $date = null)
4518 if (isset($this->currentCert['tbsCertList'])) {
4519 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
4520 if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked
4521 if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
4522 if (!empty($date)) {
4523 $rclist[$i]['revocationDate'] = $this->_timeField($date);
4536 * Unrevoke a certificate.
4538 * @param string $serial
4542 function unrevoke($serial)
4544 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
4545 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4547 $rclist = array_values($rclist);
4556 * Get a revoked certificate.
4558 * @param string $serial
4562 function getRevoked($serial)
4564 if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
4565 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4574 * List revoked certificates
4576 * @param array $crl optional
4580 function listRevoked($crl = null)
4583 $crl = $this->currentCert;
4586 if (!isset($crl['tbsCertList'])) {
4592 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
4593 foreach ($rclist as $rc) {
4594 $result[] = $rc['userCertificate']->toString();
4602 * Remove a Revoked Certificate Extension
4604 * @param string $serial
4609 function removeRevokedCertificateExtension($serial, $id)
4611 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
4612 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4613 return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4621 * Get a Revoked Certificate Extension
4623 * Returns the extension if it exists and false if not
4625 * @param string $serial
4627 * @param array $crl optional
4631 function getRevokedCertificateExtension($serial, $id, $crl = null)
4634 $crl = $this->currentCert;
4637 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
4638 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4639 return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4647 * Returns a list of all extensions in use for a given revoked certificate
4649 * @param string $serial
4650 * @param array $crl optional
4654 function getRevokedCertificateExtensions($serial, $crl = null)
4657 $crl = $this->currentCert;
4660 if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
4661 if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
4662 return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4670 * Set a Revoked Certificate Extension
4672 * @param string $serial
4674 * @param mixed $value
4675 * @param bool $critical optional
4676 * @param bool $replace optional
4680 function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
4682 if (isset($this->currentCert['tbsCertList'])) {
4683 if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
4684 if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
4685 return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
4694 * Extract raw BER from Base64 encoding
4697 * @param string $str
4700 function _extractBER($str)
4702 /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
4703 * above and beyond the ceritificate.
4704 * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
4707 * localKeyID: 01 00 00 00
4708 * subject=/O=organization/OU=org unit/CN=common name
4709 * issuer=/O=organization/CN=common name
4711 $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
4712 // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
4713 $temp = preg_replace('#-+[^-]+-+#', '', $temp);
4715 $temp = str_replace(array("\r", "\n", ' '), '', $temp);
4716 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false;
4717 return $temp != false ? $temp : $str;
4721 * Returns the OID corresponding to a name
4723 * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
4724 * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
4725 * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
4726 * to work from version to version.
4728 * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
4729 * what's being passed to it already is an OID and return that instead. A few examples.
4731 * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
4732 * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
4733 * getOID('zzz') == 'zzz'
4738 function getOID($name)
4741 if (!isset($reverseMap)) {
4742 $reverseMap = array_flip($this->oids);
4744 return isset($reverseMap[$name]) ? $reverseMap[$name] : $name;