phpseclib: OpenSSL vs phpseclib

Download phpseclib

Unlike OpenSSL, phpseclib can:

OpenSSL's PHP bindings offer a great many features phpseclib (currently) does not, however, phpseclib offers some features OpenSSL's PHP bindings do not.

Verify a signature

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.

Create a certificate without the private key

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";

Setting CSR attributes and extension requests

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";

Update certificate request before signing

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";

Renewing a certificate

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";

Create certificate revocation lists

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";

Check if a certificate has been revoked

<?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");

Update and renew a certificate revocation list

<?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";