To understand phpseclib's X.509 API it first helps to consider what an X.509 certificate aims to do. So let's start off by decoding one.
Here's google.com's X.509 cert:
Let's use phpseclib to decode that:
<?php include('File/X509.php'); $x509 = new File_X509(); $cert = $x509->loadX509('...'); print_r($cert); ?>
Specific values can be retrieved by accessing the appropriate array indices in $cert
or with the following (self-explanatory) functions:
getDNProp()
: See Distinguished Names for more information.getDN()
getIssuerDN()
getPublicKey()
: Returns a Crypt_RSA objectThe contents of $cert
are as follows:
$cert
:
When you connect to an HTTPS website you're given that website's public key via an X.509 certificate. The question is... how do you know that that public key hasn't, itself, been tampered with? SSH asks you if the certificate is valid upon your first connection attempt and alerts you when the certificate changes on subsequent connection attempts. HTTPS uses certificate authorities to verify the legitimacy of a public key. Certificate authorities have public keys as well, that are distributed via X.509 certificates. A website's X.509 certificate is signed by a certificate authorities private key and then verified by the certificate authorities public key.
$cert['tbsCertificate']['issuer']
:$cert['tbsCertificate']['subject']
:$cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
:$cert['signature']
:$rsa->sign($cert['tbsCertificate'])
with the certificate authorities private key.
Let's try to save that X.509 cert as is:
<?php include('File/X509.php'); $x509 = new File_X509(); $cert = $x509->loadX509('...'); echo $x509->saveX509($cert); ?>
Here's the result:
Great! So we have a valid X.509 cert, right? Not quite. Although it'll decode as one it's not going to properly validate. Even though nothing in the certificate has been changed (which one could do by editing the associative array) X.509 certificates have certain optional fields that when not present default to certain values. Some encoders may save these values explicitly, which would change the DER encoding of the tbsCertificate portion of the certificate which is what the signature is based off of.
To validate a cert that certs signing cert needs to be loaded. This can be done by calling $x509->loadCA('...')
. After that one need simply call $x509->validateSignature()
. Here's an example:
<?php include('File/X509.php'); $x509 = new File_X509(); $x509->loadCA('...'); $cert = $x509->loadX509('...'); echo $x509->validateSignature() ? 'valid' : 'invalid'; ?>
The certificate used to sign google.com's certificate is as follows:
Pass that cert into $x509->loadCA()
and then pass Certificate 1 into $x509->loadX509()
and the above code will output "valid". Use Certificate 2 and the above code will output "invalid".
Other validation methods are as follows:
$x509->validateSignature(FILE_X509_VALIDATE_SIGNATURE_BY_CA)
does not accept self-signed certs as being valid.$x509->validateURL()
$x509->validateDate()
validates against the current date.$x509->validateDate('June 1, 1983')
validates against June 1, 1983As Creating an X.509 cert fail demonstrated, simply saving an X.509 associative array is insufficient. The array needs to be properly signed. This can be done by calling $x509->sign($issuer, $subject)
.
The following is a discussion of the various options that can be set on each of the parameters in the $x509->sign($issuer, $subject)
call. Feel free to skip ahead to the example.
$issuer
properties consist of the following:
$cert['tbsCertificate']['issuer']
$issuer->setPrivateKey()
$cert['signature']
)$issuer->setKeyIdentifier()
. It's purpose is beyond the scope of this tutorial.id-ce-authorityKeyIdentifier
extension$subject
properties consist of the following:
$cert['tbsCertificate']['subject']
$subject->setPublicKey()
$cert['tbsCertificate']['subjectPublicKeyInfo']
$subject->setDomain()
$cert['tbsCertificate']['subject']
(id-at-commonName) and potentially the id-ce-subjectAltName
extension.$issuer->setKeyIdentifier()
. It's purpose is beyond the scope of this tutorial.id-ce-subjectKeyIdentifier
extension$x509
properties (ie. those that don't belong to either the $issuer
or $subject
consist of the following:
$x509->setStartDate()
. Accepts anything strtotime()
accepts. Defaults to the current time.$cert['tbsCertificate']['validity']['notBefore']['utcTime']
$x509->setEndDate()
. Accepts anything strtotime()
accepts. Defaults to one year from now$cert['tbsCertificate']['validity']['notAfter']['utcTime']
$x509->setSerialNumber()
. Defaults to 0.$cert['tbsCertificate']['serialNumber']
$x509->makeCA()
. Needs to be set if you want to import cert into browser as CA.id-ce-keyUsage
and id-ce-basicConstraints
<?php include('File/X509.php'); include('Crypt/RSA.php'); // create private key / x.509 cert for stunnel / website $privKey = new Crypt_RSA(); extract($privKey->createKey()); $privKey->loadKey($privatekey); $pubKey = new Crypt_RSA(); $pubKey->loadKey($publickey); $pubKey->setPublicKey(); $subject = new File_X509(); $subject->setPublicKey($pubKey); $subject->setDNProp('id-at-organizationName', 'phpseclib demo cert'); $subject->setDomain('www.whatever.com'); $issuer = new File_X509(); $issuer->setPrivateKey($privKey); $issuer->setDN($subject->getDN()); $x509 = new File_X509(); //$x509->setStartDate('-1 month'); // default: now //$x509->setEndDate('+1 year'); // default: +1 year //$x509->setSerialNumber(chr(1)); // default: 0 $result = $x509->sign($issuer, $subject); echo "the stunnel.pem contents are as follows:\r\n\r\n"; echo $privKey->getPrivateKey(); echo "\r\n"; echo $x509->saveX509($result); echo "\r\n";
The output of the above is as follows:
Distinguished Names can be set by any of the following functions:
$subject->loadX509('...')
will set the $subject
object to use the certificate subject's distinguished name as its own$subject->setDNProp('propName', 'propValue')
accepts propName's like id-at-commonName
(used in the X.509 specs) or CN
(used by OpenSSL as short hand)$subject->setDN('...')
takes stuff like $cert['tbsCertificate']['subject']
, the $code['subject']
output of $cert = openssl_x509_parse('...')
and full-on DN strings. eg:
C=US, ST=California, L=Mountain View, O=Google Inc, CN=www.google.com
$subject->loadCSR('...')
gets the distinguished name from a certificate signing request$subject->removeDNProp('propName')
Here we just create a self-signed cert and its corresponding private key, as above, that'll serve as the CA, and then another cert, signed by the CA, with a second private key to correspond to this second cert.
CA certs need to have $x509->makeCA()
called on them for browsers to accept them as certificate authorities.
<?php include('File/X509.php'); include('Crypt/RSA.php'); // create private key for CA cert // (you should probably print it out if you want to reuse it) $CAPrivKey = new Crypt_RSA(); extract($CAPrivKey->createKey()); $CAPrivKey->loadKey($privatekey); $pubKey = new Crypt_RSA(); $pubKey->loadKey($publickey); $pubKey->setPublicKey(); echo "the private key for the CA cert (can be discarded):\r\n\r\n"; echo $privatekey; echo "\r\n\r\n"; // create a self-signed cert that'll serve as the CA $subject = new File_X509(); $subject->setPublicKey($pubKey); $subject->setDNProp('id-at-organizationName', 'phpseclib demo CA'); $issuer = new File_X509(); $issuer->setPrivateKey($CAPrivKey); $issuer->setDN($CASubject = $subject->getDN()); $x509 = new File_X509(); $x509->setStartDate('-1 month'); $x509->setEndDate('+1 year'); $x509->setSerialNumber(chr(1)); $x509->makeCA(); $result = $x509->sign($issuer, $subject); echo "the CA cert to be imported into the browser is as follows:\r\n\r\n"; echo $x509->saveX509($result); echo "\r\n\r\n"; // create private key / x.509 cert for stunnel / website $privKey = new Crypt_RSA(); extract($privKey->createKey()); $privKey->loadKey($privatekey); $pubKey = new Crypt_RSA(); $pubKey->loadKey($publickey); $pubKey->setPublicKey(); $subject = new File_X509(); $subject->setPublicKey($pubKey); $subject->setDNProp('id-at-organizationName', 'phpseclib demo cert'); $subject->setDomain('www.google.com'); $issuer = new File_X509(); $issuer->setPrivateKey($CAPrivKey); $issuer->setDN($CASubject); $x509 = new File_X509(); $x509->setStartDate('-1 month'); $x509->setEndDate('+1 year'); $x509->setSerialNumber(chr(1)); $result = $x509->sign($issuer, $subject); echo "the stunnel.pem contents are as follows:\r\n\r\n"; echo $privKey->getPrivateKey(); echo "\r\n"; echo $x509->saveX509($result); echo "\r\n";
The output of the above is as follows: