OpenSSL's PHP bindings offer a great many features phpseclib (currently) does not, however, phpseclib offers some features OpenSSL's PHP bindings do not.
OpenSSL's PHP bindings do not provide an easy way for you to verify that a certificate was signed by a certificate authority (or even that it was self-signed). phpseclib does.
<?php $x509 = new File_X509(); $x509->loadCA('...'); // see googleca.pem $x509->loadX509('...'); // see google2.pem echo $x509->validateSignature() ? 'valid' : 'invalid';
Here's how you'd do it with OpenSSL's PHP bindings:
<?php function pem2der($str) { $temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $str, 1); // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff $temp = preg_replace('#-+[^-]+-+#', '', $temp); // remove new lines $temp = str_replace(array("\r", "\n", ' '), '', $temp); $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; if ($temp != false) { $str = $temp; } return $str; } // the offsets were calculated by analyzing the output of asn1parse.php with // google2.pem $subject = '...'; // see google2.pem $subject = pem2der($subject); // in asn1tutorial.html we don't want the header since we're trying to see in // the BIT STRING. in this case, however, we do want the header to be included. // that means we also have to add the header length to the content length $data = substr($subject, 4, 862 + 4); // to get the hash, however, we do need to peer inside the BIT STRING, as is done in asn1tutorial.html $signature = substr($subject, 885 + 4 + 1, 257); $pubKey = openssl_pkey_get_public('...'); // see googleca.pem echo openssl_verify($data, $signature, $pubKey) ? 'valid' : 'invalid';
Validate another certificate and you'd have to calculate the offsets all over again. And to do that you'd need to have some way of parsing ASN1. phpseclib takes care of all of this for you.
Unlike OpenSSL, phpseclib does not need a certificate request to create a certificate: since certificate requests have to be signed with the private key, OpenSSL is unable to produce a certificate without knowing the private key.
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Load the CA and its private key. $pemcakey = file_get_contents('myCAprivkey.pem'); $cakey = new Crypt_RSA(); $cakey->loadKey($pemcakey); $pemca = file_get_contents('myCA.pem'); $ca = new File_X509(); $ca->loadX509($pemca); $ca->setPrivateKey($cakey); // Load the certificate public key. $pubkey = new Crypt_RSA(); $pubkey->loadKey(file_get_contents('pubkey.pem')); $pubkey->setPublicKey(); // Build the new certificate. $x509 = new File_X509(); $x509->loadCA($pemca); $x509->setPublicKey($pubkey); $x509->setDN('CN=www.example.org'); $x509->setStartDate('-1 day'); $x509->setEndDate('+ 1 year'); $x509->setSerialNumber('1234', 10); // Sign new certificate. $cert = $x509->sign($ca, $x509); // Output it. echo $x509->saveX509($cert) . "\n";
OpenSSL can only do it via a configuration file.
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Create key pair. $rsa = new Crypt_RSA(); $key = $rsa->createKey(); $privkey = new Crypt_RSA(); $privkey->loadKey($key['privatekey']); $pubkey = new Crypt_RSA(); $pubkey->loadKey($key['publickey']); $pubkey->setPublicKey(); // Create certificate request. $csr = new File_X509(); $csr->setPrivateKey($privkey); $csr->setPublicKey($pubkey); $csr->setDN('CN=www.example.org'); $csr->loadCSR($csr->saveCSR($csr->signCSR())); // Set CSR attribute. $csr->setAttribute('pkcs-9-at-unstructuredName', array('directoryString' => array('utf8String' => 'myCSR')), FILE_X509_ATTR_REPLACE); // Set extension request. $csr->setExtension('id-ce-keyUsage', array('encipherOnly')); // Generate CSR. echo $csr->saveCSR($csr->signCSR()) . "\n";
Since OpenSSL can only produce certificate from valid certificate requests, there is no way to alter subject and/or extensions before signing. These features are available in phpseclib.
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Read certificate request and validate it. $csr = new File_X509(); $csr->loadCSR(file_get_contents('mycsr.pem')); if ($csr->validateSignature() !== 1) { exit("Invalid CSR\n"); } // Alter certificate request. $csr->setDNProp('CN', 'www.example.org'); $csr->removeExtension('id-ce-basicConstraints'); // Load the CA and its private key. $pemcakey = file_get_contents('myCAprivkey.pem'); $cakey = new Crypt_RSA(); $cakey->loadKey($pemcakey); $pemca = file_get_contents('myCA.pem'); $ca = new File_X509(); $ca->loadX509($pemca); $ca->setPrivateKey($cakey); // Sign the updated request, producing the certificate. $x509 = new File_X509(); $cert = $x509->loadX509($x509->saveX509($x509->sign($ca, $csr))); // Generate the certificate. echo $x509->saveX509($cert) . "\n";
OpenSSL can only sign a certificate request, but the certificate's private key is needed to produce this certificate request. By directly resigning a certificate, phpseclib can renew it.
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Load the CA and its private key. $pemcakey = file_get_contents('myCAprivkey.pem'); $cakey = new Crypt_RSA(); $cakey->loadKey($pemcakey); $pemca = file_get_contents('myCA.pem'); $ca = new File_X509(); $ca->loadX509($pemca); $ca->setPrivateKey($cakey); // Read the old certificate. $oldcert = new File_X509(); $oldcert->loadCA($pemca); $oldcert->loadX509(file_get_contents('oldcert.pem')); if ($oldcert->validateSignature() !== 1) { exit("Cannot validate old certificate\n"); } // Set new dates and serial number. $newcert = new File_X509(); $newcert->setStartDate('-1 day'); $newcert->setEndDate('+2 years'); $newcert->setSerialNumber('1234', 10); // Produce the new certificate by signing the old one. $crt = $newcert->loadX509($newcert->saveX509($newcert->sign($ca, $oldcert))); // Output new certificate. echo $newcert->saveX509($crt) . "\n";
Although requested to PHP openSSL support for several years, certificate revocation lists are still not implemented in standard PHP: https://bugs.php.net/bug.php?id=40046.
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Load the CA and its private key. $pemcakey = file_get_contents('myCAprivkey.pem'); $cakey = new Crypt_RSA(); $cakey->loadKey($pemcakey); $pemca = file_get_contents('myCA.pem'); $ca = new File_X509(); $ca->loadX509($pemca); $ca->setPrivateKey($cakey); // Build the (empty) certificate revocation list. $crl = new File_X509(); $crl->loadCRL($crl->saveCRL($crl->signCRL($ca, $crl))); // Revoke a certificate. $crl->setRevokedCertificateExtension('1234', 'id-ce-cRLReasons', 'privilegeWithdrawn'); // Sign the CRL. $crl->setSerialNumber(1, 10); $crl->setEndDate('+3 months'); $newcrl = $crl->signCRL($ca, $crl); // Output it. echo $crl->saveCRL($newcrl) . "\n";
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Load the CA and its private key. $pemcakey = file_get_contents('myCAprivkey.pem'); $cakey = new Crypt_RSA(); $cakey->loadKey($pemcakey); $pemca = file_get_contents('myCA.pem'); $ca = new File_X509(); $ca->loadX509($pemca); $ca->setPrivateKey($cakey); // Load the CRL. $crl = new File_X509(); $crl->loadCA($pemca); // For later signature check. $pemcrl = file_get_contents('myCRL.pem'); $crl->loadCRL($pemcrl); // Validate the CRL. if ($crl->validateSignature() !== 1) { exit("CRL signature is invalid\n"); } // Load the certificate to check. $x509 = new File_X509(); $x509->loadCA($pemca); $cert = $x509->loadX509(file_get_contents('myCert.pem')); if ($x509->validateSignature() !== 1) { exit("Certificate signature is invalid\n"); } // Get the certificate's serial number. $csn = $cert['tbsCertificate']['serialNumber']->toString(); // Check certificate revocation status. $revoked = $crl->getRevoked($csn); if (empty($revoked)) { exit("certificate is not revoked\n"); } echo 'certificate has been revoked on ' . implode('', $revoked['revocationDate']); $reason = $crl->getRevokedCertificateExtension($csn, 'id-ce-cRLReasons'); if (!empty($reason)) { echo "; reason is $reason"; } exit(".\n");
<?php require_once 'Crypt/RSA.php'; require_once 'File/X509.php'; // Load the CA and its private key. $pemcakey = file_get_contents('myCAprivkey.pem'); $cakey = new Crypt_RSA(); $cakey->loadKey($pemcakey); $pemca = file_get_contents('myCA.pem'); $ca = new File_X509(); $ca->loadX509($pemca); $ca->setPrivateKey($cakey); // Load the CRL. $crl = new File_X509(); $crl->loadCA($pemca); // For later signature check. $pemcrl = file_get_contents('myCRL.pem'); $crl->loadCRL($pemcrl); // Validate the CRL. if ($crl->validateSignature() !== 1) { exit("CRL signature is invalid\n"); } // Update the revocation list. $crl->setRevokedCertificateExtension('4321', 'id-ce-cRLReasons', 'privilegeWithdrawn'); // Generate the new CRL. $crl->setEndDate('+3 months'); $newcrl = $crl->signCRL($ca, $crl); // Output it. echo $crl->saveCRL($newcrl) . "\n";