X.509
Reading Certificates
use phpseclib3\File\X509;
$x509 = new X509();
$cert = $x509->loadX509(file_get_contents('google.crt'));
var_dump($cert);
Running the above will produce an array that looks something like this:
$cert
- tbsCertificate
- version
- v3
- serialNumber
- 105827261859531100510423749949966875981
- signature
- algorithm
- sha1WithRSAEncryption
- parameters
- null
- null
- algorithm
- issuer
- rdnSequence
- 0
- 0
- type
- id-at-countryName
- value
- printableString
- ZA
- printableString
- type
- 0
- 1
- 0
- type
- id-at-organizationName
- value
- printableString
- Thawte Consulting (Pty) Ltd.
- printableString
- type
- 0
- 2
- 0
- type
- id-at-commonName
- value
- printableString
- Thawte SGC CA
- printableString
- type
- 0
- 0
- rdnSequence
- validity
- notBefore
- utcTime
- Wed, 26 Oct 2011 00:00:00 +0000
- utcTime
- notAfter
- utcTime
- Mon, 30 Sep 2013 23:59:59 +0000
- utcTime
- notBefore
- subject
- rdnSequence
- 0
- 0
- type
- id-at-countryName
- value
- printableString
- US
- printableString
- type
- 0
- 1
- 0
- type
- id-at-stateOrProvinceName
- value
- printableString
- California
- printableString
- type
- 0
- 2
- 0
- type
- id-at-localityName
- value
- teletexString
- Mountain View
- teletexString
- type
- 0
- 3
- 0
- type
- id-at-organizationName
- value
- teletexString
- Google Inc
- teletexString
- type
- 0
- 4
- 0
- type
- id-at-commonName
- value
- teletexString
- www.google.com
- teletexString
- type
- 0
- 0
- rdnSequence
- subjectPublicKeyInfo
- algorithm
- algorithm
- rsaEncryption
- parameters
- null
- null
- algorithm
- subjectPublicKey
-
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDetyZDppmFzTinFQm5zw/Jw1WM iO6MjSgnJEsqXqDYFvphGEvPbWCA0zVAMnLAjxLY5U6PubL22RVeWoYxo7qGqmvI 2XGMzM0nEx6dQl049qes7/pi8xiB1CRGfwF3fMYqiRSZu5g5HagZ+zkARH0blGp4 LWmtwHos+tDaIBKY0wIDAQAB -----END PUBLIC KEY-----
-
- algorithm
- extensions
- 0
- extnId
- id-ce-basicConstraints
- critical
- 1
- extnValue
- cA
- cA
- extnId
- 1
- extnId
- id-ce-cRLDistributionPoints
- critical
- extnValue
- 0
- distributionPoint
- fullName
- 0
- uniformResourceIdentifier
- http://crl.thawte.com/ThawteSGCCA.crl
- uniformResourceIdentifier
- 0
- fullName
- distributionPoint
- 0
- extnId
- 2
- extnId
- id-ce-extKeyUsage
- critical
- extnValue
- 0
- id-kp-serverAuth
- 1
- id-kp-clientAuth
- 2
- 2.16.840.1.113730.4.1
- 0
- extnId
- 3
- extnId
- id-pe-authorityInfoAccess
- critical
- extnValue
- 0
- accessMethod
- id-ad-ocsp
- accessLocation
- uniformResourceIdentifier
- http://ocsp.thawte.com
- uniformResourceIdentifier
- accessMethod
- 1
- accessMethod
- id-ad-caIssuers
- accessLocation
- uniformResourceIdentifier
- http://www.thawte.com/repository/Thawte_SGC_CA.crt
- uniformResourceIdentifier
- accessMethod
- 0
- extnId
- 0
- version
- signatureAlgorithm
- algorithm
- sha1WithRSAEncryption
- parameters
- null
- null
- algorithm
- signature
- ...
getDNProp()
print_r($x509->getDNProp('CN'));
That will produce the following:
- 0
- www.google.com
An array is returned because each distinguished name property can (in theory) have multiple values
Valid property names are enumerated upon at Distinguished Property Names.
getIssuerDNProp()
returns the issuer distinguished name as opposed to the subject distinguished name.
getDN()
print_r($x509->getDN());
getDN()
/ getIssuerDN()
accept several different parameters:
X509::DN_ARRAY
(the default value) returns an array who's keys are based on the ASN.1 syntax for X.509:- rdnSequence
- 0
- 0
- type
- id-at-countryName
- value
- printableString
- US
- printableString
- type
- 0
- 1
- 0
- type
- id-at-stateOrProvinceName
- value
- printableString
- California
- printableString
- type
- 0
- 2
- 0
- type
- id-at-localityName
- value
- teletexString
- Mountain View
- teletexString
- type
- 0
- 3
- 0
- type
- id-at-organizationName
- value
- teletexString
- Google Inc
- teletexString
- type
- 0
- 4
- 0
- type
- id-at-commonName
- value
- teletexString
- www.google.com
- teletexString
- type
- 0
- 0
- rdnSequence
X509::DN_STRING
returns an OpenSSL-style string:C=US, ST=California, L=Mountain View, O=Google Inc, CN=www.google.com
X509::DN_OPENSSL
returns an OpenSSL-style array:- C
- US
- ST
- California
- L
- Mountain View
- O
- Google Inc
- CN
- www.google.com
- C
X509::DN_ASN1
returns a DER encoded binary stringX509::DN_CANON
returns a "canonicalized" DER encoded binary string wherein SEQUENCE around RDNs and all string values normalized as trimmed lowercase UTF-8 with all spacing as one blank. Constructed RDNs are not canonicalized.
getPublicKey()
echo $x509->getPublicKey();
Returns a \phpseclib3\Crypt\Common\PublicKey
object that, by default, gets cast to a PKCS8-encoded public key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDetyZDppmFzTinFQm5zw/Jw1WM
iO6MjSgnJEsqXqDYFvphGEvPbWCA0zVAMnLAjxLY5U6PubL22RVeWoYxo7qGqmvI
2XGMzM0nEx6dQl049qes7/pi8xiB1CRGfwF3fMYqiRSZu5g5HagZ+zkARH0blGp4
LWmtwHos+tDaIBKY0wIDAQAB
-----END PUBLIC KEY-----
Validating Certificates
Signatures
$x509 = new X509();
$x509->loadCA('...');
$cert = $x509->loadX509('...');
echo $x509->validateSignature() ? 'valid' : 'invalid';
Certificate authority certificates can be downloaded from curl - SSL CA Certificates. Parsing that is left as an exercise to the reader.
Self-signed Signatures
$x509 = new X509();
$cert = $x509->loadX509('...');
echo $x509->validateSignature(false) ?
'valid' :
'invalid';
URLs
$x509 = new File_X509();
$cert = $x509->loadX509('...');
echo $x509->validateURL('https://www.domain.tld/path/to/whatever.ext') ?
'valid' :
'invalid';
Dates
$x509 = new X509();
$cert = $x509->loadX509('...');
echo $x509->validateDate() ? 'valid' : 'invalid';
By default validateDate
checks to see if the current date is within the date range. If, however, you want to use a custom date you may do so by either passing a string (eg. validateDate('January 1, 2001')
) or a DateTime object (eg. validateDate(new DateTime('January 1, 2001'))
).
Chains
Let's say you have a certificate chain that has at least one intermediate cert and let's further say that you wanted to see all the certs in this chain. To do that you could do $x509>getChain()
. That'll return an array of X509 objects who's first element is the current cert and who's last element is the CA cert.
If you want to retrieve the array contents of any cert in the chain you'll need to do $x509->getCurrentCert()
.
Creating Certificates
The General Idea
In a nutshell...
$result = $x509->sign($issuer, $subject);
$x509->saveX509($result);
Public / Private Keys
$issuer
and $subject
are, themselves, X509 objects. They need private and public keys, respectively. Here's an example of how to create them from scratch for a brand new self-signed cert:
$privKey = RSA::createKey();
$pubKey = $privKey->getPublicKey();
$issuer->setPrivateKey($privKey);
$subject->setPublicKey($pubKey);
The public key can also be set by $subject->loadCSR('...')
or $subject->loadX509('...')
. If you're using $subject->loadX509()
you'll effectively be re-signing $subject
. The issuer DN of $subject
will be updated as will some of the other "transactional properties" (see below), if appropriate, but all (well, most) of the extensions will be preserved.
Note that whereas in the 1.0 and 2.0 branches, the signature algorithm was set by using an additional parameter in the sign
method (or signCSR
, signCRL
, etc) in this case it's set based on the key. So if you want to create an rsaEncryption X509 cert you'd need to do $publicKey = $publicKey->withHashing(RSA::SIGNATURE_PKCS1)
since, by default, RSA keys use RSA::SIGNATURE_PSS
.
Key Identifiers
Sometimes an $issuer
or a $subject
will have multiple private keys. To disambiguate between them key identifiers are used. Quoting RFC5280 § 4.2.1.1, "the value of the keyIdentifier field SHOULD be derived from the public key used to verify the certificate's signature or a method that generates unique values". An example follows:
$issuer->setKeyIdentifier($issuer->computeKeyIdentifier($pubKey));
Note that computeKeyIdentifier is already called by saveX509()
unless you've specifically over-written the key identifier.
Distinguished Names
$issuer
and $subject
also need to have their distinguished names set. That can be done in a number of ways. The following are all equivalent to one another:
$subject->setDNProp('id-at-organizationName', 'phpseclib demo cert');
$subject->setDN(
array(
'rdnSequence' => array(
array(
array(
'type' => 'id-at-organizationName',
'value'=> 'phpseclib demo cert'
)
)
)
)
);
$subject->setDN(array(
'O' => 'phpseclib demo cert'
));
$subject->setDN('/O=phpseclib demo cert');
DN properties can also be removed thusly:
$subject->removeDNProp('id-at-organizationName');
DN properties can be set via $subject->loadCSR('...')
and $subject->loadX509('...')
as well.
In the case of $subject->setDN()
.. the value will be assumed to be utf8String unless you explicitely tell phpseclib to set it to another type by doing something like 'value'=> array('ia5String' => 'phpseclib demo cert')
.
See also: List of DN property names
Domain Names
If your X.509 certificate is supposed to be valid for a single domain name that domain name can be set by either setting the id-at-commonName or CN distinguished name property. If your X.509 certificate is supposed to be valid for multiple domain names an X.509 extension - id-ce-subjectAltName - is required. setDomain()
takes care of all of this for you. Here's an example of how to make your certificate valid for two domains:
$subject->setDomain('www.google.com', 'www.yahoo.com');
Transactional Properties
Some properties belong neither to $issuer
or $subject
. Rather they belong to "transaction". The functions to set those properties are as follows:
$x509->setSerialNumber(...);
$x509->makeCA();
$x509->setStartDate(...);
$x509->setEndDate(...);
$subject->setDomain()
isn't considered a transactional property because, in theory, the issuing certificate could have at least one domain too since that's based on a particular distinguished name property.
The start time is, by default, when the cert is created. The current time is converted to UTC time and the fact that it's UTC time is denoted in the cert. Other X.509 decoders (eg. browsers or email clients or whatever) should decode this to their timezone so there's no need to set it to do $x509->setStartDate('-1 day')
or anything like that.
The end date, by default, is one year from the current time.
setStartDate()
/ setEndDate()
can accept strings or DateTime objects. If you want the cert to last forever pass 'lifetime'
to it.
Function List
Functions affecting properties common to both $issuer
and $subject
are as follows:
setDNProp
removeDNProp
setDN
loadCSR
loadX509
setKeyIdentifier / computeKeyIdentifier
Functions affecting $issuer
only properties:
setPrivateKey
Functions affecting $subject
only properties:
setPublicKey
setDomain
Functions affecting $x509
properties:
setSerialNumber
makeCA
setStartDate
setEndDate
Minimalistic Example
$subject = new X509();
$subject->setPublicKey($pubKey); // $pubKey is a PublicKey objet
$subject->setDN('/O=phpseclib demo cert');
$issuer = new X509();
$issuer->setPrivateKey($privKey); // $privKey is a PrivateKey object
$issuer->setDN('/O=phpseclib demo cert');
$x509 = new X509();
$result = $x509->sign($issuer, $subject);
echo $x509->saveX509($result);
For self-signed certs the DN of the subject and issuer will match and the issuer's private key will correspond to the subject's public key.
Example: Self-signed cert
use phpseclib3\File\X509;
use phpseclib3\Crypt\RSA;
// create private key / x.509 cert for stunnel / website
$CAPrivKey = RSA::createKey();
$CAPubKey = $CAPrivKey->getPublicKey();
$CASubject = new X509;
$CASubject->setDNProp('id-at-organizationName', 'phpseclib CA cert');
$CASubject->setPublicKey($CAPubKey);
$CAIssuer = new X509;
$CAIssuer->setPrivateKey($CAPrivKey);
$CAIssuer->setDN($CASubject->getDN());
$x509 = new X509;
$x509->makeCA();
$result = $x509->sign($CAIssuer, $CASubject);
echo "private key for CA cert (can be discarded):\r\n\r\n";
echo $CAPrivKey;
echo "\r\n\r\nCA cert to be imported into browser:\r\n\r\n";
echo $x509->saveX509($result);
echo "\r\n";
The cert that this script creates is a CA cert. If you don't want it to be a CA cert you can comment out the $x509->makeCA()
line.
Example: CA-signed cert
The following code requires the previous code to work and should follow the code in the previous section.
$privKey = RSA::createKey();
$pubKey = $privKey->getPublicKey();
$subject = new X509;
$subject->setDNProp('id-at-organizationName', 'phpseclib demo cert');
$subject->setPublicKey($pubKey);
$x509 = new X509;
$result = $x509->sign($CAIssuer, $subject);
echo "\r\nthe stunnel.pem contents are as follows:\r\n\r\n";
echo $privKey;
echo "\r\n";
echo $x509->saveX509($result);
echo "\r\n";
Example: Arbitrary and Custom Extensions
phpseclib can be used to implement add arbitrary extensions as well.
Building off of the previous example (using the phpBB MOD Text Template):
#
#-----[ FIND ]------------------------------------------
#
$result = $x509->sign($issuer, $subject);
#
#-----[ BEFORE, ADD ]-----------------------------------
#
$x509->setExtensionValue('id-pe-authorityInfoAccess', [
[
"accessMethod" => "id-ad-ocsp",
"accessLocation" => [
"uniformResourceIdentifier" => 'https://ocsp.test.ca/',
]
],
[
"accessMethod" => "id-ad-caIssuers",
"accessLocation" => [
"uniformResourceIdentifier" => 'https://crt.test.ca/test.crt',
],
],
], true);
The first two parameters are pretty self explanatory. The third parameter is used to denote if the extension is critical or not. A fourth parameter - $replace
- also exists if you want this extension to replace an existant extension with the same id as the one you're adding. This parameter defaults to false
(don't replace) but can also be set to true
(do replace).
Now let's say you wanted to include not just an arbitrary extension but a custom one as well (eg. one that phpseclib doesn't have pre built-in support for). That can be done thusly:
#
#-----[ FIND ]------------------------------------------
#
use phpseclib3\File\X509;
use phpseclib3\Crypt\RSA;
#
#-----[ AFTER, ADD ]------------------------------------
#
use phpseclib3\File\ASN1;
$customExtensionName = 'cust';
$customExtensionNumber = '2.16.840.1.101.3.4.2.99';
ASN1::loadOIDs([
$customExtensionName => $customExtensionNumber,
]);
X509::registerExtension($customExtensionName, [
'type' => ASN1::TYPE_SEQUENCE,
'children' => [
'toggle' => ['type' => ASN1::TYPE_BOOLEAN],
'num' => ['type' => ASN1::TYPE_INTEGER],
'name' => ['type' => ASN1::TYPE_OCTET_STRING],
'list' => [
'type' => ASN1::TYPE_SEQUENCE,
'min' => 0,
'max' => -1,
'children' => ['type' => ASN1::TYPE_OCTET_STRING],
],
],
]);
#
#-----[ FIND ]------------------------------------------
#
$result = $x509->sign($issuer, $subject);
#
#-----[ BEFORE, ADD ]-----------------------------------
#
$x509->setExtensionValue($customExtensionName, $customExtensionData);