Reading SSL certificates in PHP

A recent project required me to write a automated SSL certificate checker that would ping me every-time a SSL renewal is due. The project required to parse a handful of client sites and get the SSL information.

Reading a SSL certificate directly from a domain is relatively simple in PHP. A working example function is given below.


/** 
 * Takes a domain name and returns SSL certificate information
 * 
 * @param string $domain_name
 * @return array $certinfo
 */
function getSSL($domain_name)
{
    $errno = 0;
    $errstr = '';
    
    // Set socket connection timeout
    $timeout = 30;

    // Create a stream context to open a ssl socket
    // Set options to get ssl certificate
    // https://www.php.net/manual/en/context.php
    // https://www.php.net/manual/en/context.ssl.php

    $ssl_info = stream_context_create(array(
                                        "ssl" => array(
                                        "capture_peer_cert" => TRUE)));

    $stream = stream_socket_client("ssl://" . $domain_name . ":443", 
                                    $errno, 
                                    $errstr, 
                                    $timeout, 
                                    STREAM_CLIENT_CONNECT, 
                                    $ssl_info);

    if (!$stream) {
        echo "ERROR: $errno - $errstr";
    } else {
        $cert_resource = stream_context_get_params($stream);
        $certificate = $cert_resource['options']['ssl']['peer_certificate'];
        $certinfo = openssl_x509_parse($certificate);
        fclose($stream);
        return $certinfo;
    }
}

Reading SSL certificate from a web domain

Sample request to read a SSL certificate information from a website is given below.

$certinfo = getSSL("codediesel.com");
print_r($certinfo);
// print_r($certinfo) output

[O] => ZeroSSL
            [CN] => ZeroSSL RSA Domain Secure Site CA
        )

    [version] => 2
    [serialNumber] => 0x8A1AB23A8C5EA0EEA9F22A26B58F9626
    [serialNumberHex] => 8A1AB23A8C5EA0EEA9F22A26B58F9626
    [validFrom] => 201207000000Z
    [validTo] => 210307235959Z
    [validFrom_time_t] => 1607299200
    [validTo_time_t] => 1615161599
    [signatureTypeSN] => RSA-SHA384
    [signatureTypeLN] => sha384WithRSAEncryption
    [signatureTypeNID] => 669
    [purposes] => Array
        (
            [1] => Array
                (
                    [0] => 1
                    [1] => 
                    [2] => sslclient
                )
.
.
.

    Signature : ecdsa-with-SHA256
                30:45:02:21:00:86:8F:B0:60:31:E9:27:06:EE:37:FB:
                F3:05:8C:BE:CA:6C:81:81:0B:E0:48:4B:DF:CB:39:4E:
                6B:04:48:A1:46:02:20:08:3D:0E:8A:A9:E2:0E:6B:6B:
                BB:C5:BE:72:3F:DF:08:E9:0D:B5:E4:1C:AE:B3:BC:D1:
                DC:AF:B7:F1:34:59:D1
            [subjectAltName] => DNS:codediesel.com, DNS:www.codediesel.com
        )

)

We can read the validity of the certificate using the fields retrieved from the certificate.


$valid_from = date(DATE_RFC2822, $certinfo['validFrom_time_t']);
$valid_to = date(DATE_RFC2822, $certinfo['validTo_time_t']);

Or count the number of days to expire.


if( $certinfo['validFrom_time_t'] > time() || $certinfo['validTo_time_t'] < time() ) {
    print "Certificate is expired.";
} else {
    $start = date_create(date(DATE_RFC2822, $certinfo['validTo_time_t']));
    $end   = date_create(); // Current time and date
    $diff  = date_diff( $start, $end );
    print "Certificate will expire in "  . $diff->days . " days";
}

Note: You need to set the default timezone in your code to get accurate results regarding time calculations. More discussion can be found at the following link:
https://stackoverflow.com/questions/2040560/finding-the-number-of-days-between-two-dates

Reading SSL certificate from a file

If you have a certificate file with you (usually with a .crt extension), you can use the following code to get the required information from the file.

$cert = file_get_contents('certificate.crt');
$certinfo = openssl_x509_parse($cert);
print_r($certinfo);

The string passed to the ‘openssl_x509_parse’ should be the content of a certificate, PEM encoded, which need to start with —–BEGIN CERTIFICATE—–.