Search for specific information in the digital certificate array

Asked

Viewed 185 times

1

I have a code ready that I found on the internet about Digital Certificate information for a certain domain, but I do not know how to search for specific information within this array.

I would just like to find information like certificate name, host, expiration date. Here’s an example of the code below:

 $url = "http://www.submarino.com.br";
 $orignal_parse = parse_url($url, PHP_URL_HOST);
 $get = stream_context_create(array("ssl" => array("capture_peer_cert" => TRUE)));
 $read = stream_socket_client("ssl://".$orignal_parse.":443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $get);
 $cert = stream_context_get_params($read);
 $certinfo = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);
 // print_r($certinfo);
 echo "Nome do certificado: " . $certinfo['name'];

Using the print_r in the variable $certinfo gave the following results, as it could "break" some items within the array?

  • What do you want to "break" exactly? From an example of some result, and how would you like it to be broken. It’s easier to try to help you.

1 answer

1


It’s been a while since I programmed in PHP and certainly the codes below are not the best, but as the focus of the question is on the fields of the certificate, I thought it was worth a reply.


The "name" of the certificate you refer to is probably the field Subject, that appears this way when using print_r:

[subject] => Array
    (
        [C] => BR
        [ST] => Rio de Janeiro
        [L] => Rio de Janeiro
        [O] => B2W COMPANHIA DIGITAL
        [OU] => Tecnologia
        [CN] => b2wdigital.com
    )

In case, you could only take the CN, containing the Common Name certificate (in the case of server certificates, contains the site URL):

echo "Nome do certificado: " . $certinfo['subject']['CN'];

In this case, the exit is:

Certificate name: b2wdigital.com

But a server certificate can match more than one URL, whose values are in the extension Subject Alternative Name. The print_r shows this way:

[extensions] => Array
    (
        ... outras extensões
        [subjectAltName] => DNS:b2wdigital.com, DNS:www.submarino.com, ... (um monte de entradas DNS)

In the test I did, the certificate had 31 Urls. To get them, just break the string (separates by commas) and see the entries that are "DNS":

// os valores estão separados por vírgula e espaço
$names = explode(", ", $certinfo['extensions']['subjectAltName']);
$lista_urls = []; // lista que vai guardar as URLs
foreach ($names as $name) {
    $v = explode(":", $name); // DNS: url
    if ($v[0] === "DNS") {
        $lista_urls[] = $v[1];
    }
}
print_r($lista_urls);

I put if ($v[0] === "DNS") because this extension may have other types of information than "DNS" (in this case it does not, but the definition of this extension provides for several different types). The list of Urls is quite large (from 0 to 30, omitted several for reasons of brevity):

Array
(
    [0] => b2wdigital.com
    [1] => www.submarino.com
    [2] => *.thebestoff.com.br
    [3] => *.b2w.io
    [4] => www.soubarato.com
    [5] => *.americanas.com.br
    [6] => *.soubarato.io
    [7] => *.soubarato.com.br
    [8] => *.submarino.com.br
    ....

By the way, one of the many names on this list is the URL you used to get the certificate: *.submarino.com.br.


For the expiration date, there are two fields:

[validTo] => 200204120000Z
[validTo_time_t] => 1580817600

The first is the date and time in UTC: 200204120000Z. The first two digits (20) are the year (in this case, 20 equals 2020), then comes the month (02 -> February), the day (04) and the time (120000 -> 12:00:00 -> noon). The Z in the end indicates that this date/time is in UTC.

The second value (1580817600) is the timestamp - the amount of seconds since the Unix Epoch, namely 1580817600 seconds after 1970-01-01T00:00Z.

Both match the same date/time, so you can choose anyone to get the expiration date:

// usando o valor do timestamp
$dt = new DateTime('@' . $certinfo['validTo_time_t']);
echo "Data de validade:". $dt->format('Y-m-d H:i:s P');

// usando a string 200204120000Z
$dt = DateTime::createFromFormat('ymdHisP', $certinfo['validTo']);
echo "Data de validade:". $dt->format('Y-m-d H:i:s P');

Both print:

Expiry date: 2020-02-04 12:00:00 +00:00


Other fields

To pick up more fields, just watch the exit from print_r. Your variable $certinfo nothing more than an Array with multiple entries, one for each certificate field. Some are strings, such as name:

[name] => /C=BR/ST=Rio de Janeiro/L=Rio de Janeiro/O=B2W COMPANHIA DIGITAL/OU=Tecnologia/CN=b2wdigital.com

Others may be Arrays, as in the case of subject that we saw above. Just do $certinfo['nome_do_campo'] and then see if this field is a string, Array, etc and treat it accordingly.

Moreover, name and subject are actually the same field, only in different formats (name has a string with all values and subject has these values properly broken in an Array).

This redundancy also happens with other fields, as happened with the above expiration date (the same date in 2 different formats). Another field that has this is the Serial Number, which has the same value on a decimal and hexadecimal basis:

[serialNumber] => 4187706408991266263283860591637795211
[serialNumberHex] => 032685DDE5C5A2AE141685650B49918B

Anyway, each field will have its own shape. And each certificate may or may not have certain fields (name, expiration date and serial number all have, but DNS Names do not always, for example). Unfortunately the job documentation openssl_x509_parse does not say which format of the returned Array:

The Structure of the returned data is (deliberately) not yet documented, as it is still Subject to change.

The structure of the returned data is (deliberately) undocumented as it is subject to change.

But it is possible to know what each field can have by reading the RFC 5280 (a very boring document, but very important to understand the structure of a certificate and the type of data that can be in each field).

And in the case of certificates issued in Brazil, you can consult ICP-Brasil. You can see the List of Certification Authorities, and on each one’s website must have (usually has) a link to the respective certificate policies. It is usually a PDF document detailing which fields are mandatory and optional and what you can have in each one. It is a little more specific than RFC, as it defines several optional fields and/or leaves open the types of data they may have. The certificate policy is more specific (for eCPF, for example, defines which field will have the CPF etc).

Generally the certificates have the link to the website of the Certification Authority that contains the certificate policies. In the case of the certificate in question, the URL is in the extension certificatePolicies:

    [extensions] => Array
        (
            .....
            [certificatePolicies] => Policy: 2.16.840.1.114412.1.1
  CPS: https://www.digicert.com/CPS
Policy: 2.23.140.1.2.2

Then just grab the URL that is after "CPS:"

// cada valor está em uma linha, então eu separo por \n
$policies = explode("\n", $certinfo['extensions']['certificatePolicies']);
foreach($policies as $p) {
    // limitar o array $v a no máximo 2 elementos (para não quebrar o http://...)
    $v = explode(":", $p, 2);
    if (trim($v[0]) === "CPS") {
        echo trim($v[1]); // URL da Autoridade Certificadora
    }
}

In case, the exit was:

https://www.digicert.com/CPS

Entering this site, there are several links with the text "Digicert Certificate Policy (CP)", which points to a PDF containing the certificate policy. Each link points to a different document as it depends on the date the certificate was issued (the certificate policies may change over time). In the case of the certificate in question, the date of issue is in the validFrom:

[validFrom] => 181105000000Z

In this case, November 5, 2018. Look for the link corresponding to this date and will have the correct PDF. It is also a very boring document, and usually has the definitions of each field (but can sometimes have only "generic" definitions, or say "as RFC5280" or even point to other complementary documents - anyway, it is not easy...)

See also the document of Certificate profiles, that details the fields that are mandatory and optional.

  • All right, I’ll be there later to see if it works, and as for the public key, how would you take it? And calculate how much time remains to expire from today’s date?

  • 1

    @aleque_b About the public key, here has an example. About the date, the variable $dt contains a date, and you can search (here on the site should have several examples) how to calculate difference between dates. As I said, I haven’t programmed in PHP for a long time, sorry I can’t help you more. Anyway, if you want to use the public key, then I think it is already the case to open another question, even to make the site more organized (avoid asking too many questions at once, even if related; it is better a question focused by topic)

  • Relax, I’ll test this code and give my feedback later

Browser other questions tagged

You are not signed in. Login or sign up in order to post.