97

Is there a way to programmatically check the Subject Alternative Names of a SAN SSL cert?

Using, for instance, the following command I can get many info but not all the SANs:

openssl s_client -connect www.website.example:443
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
JoeSlav
  • 4,479
  • 4
  • 31
  • 50
  • When you say programmatically, any language in particular, or are you after some shell script perhaps? – Bruno Oct 29 '12 at 19:02
  • Hi Bruno, I might have abused the term :) I just what a script or command line or .. that allows to get all ANs. – JoeSlav Oct 29 '12 at 19:57

5 Answers5

182

To get the Subject Alternative Names (SAN) for a certificate, use the following command:

openssl s_client -connect website.example:443 </dev/null 2>/dev/null | openssl x509 -noout -text | grep DNS:

First, this command connects to the site we want (website.example, port 443 for SSL):

openssl s_client -connect website.example:443

Then pipe (|) that into this command:

openssl x509 -noout -text

This takes the certificate file and outputs all its juicy details. The -noout flag keeps it from outputting the (base64-encoded) certificate file itself, which we don't need. The -text flag tells it to output the certificate details in text form.

Normally there's a whole lot of output (signature, issuer, extensions, etc) that we don't care about, so then we pipe that into a simple grep:

grep DNS:

Since the SAN entries begin with DNS: this simply returns only the lines that contain that, stripping out all the other info and leaving us with the desired information.

You may note that the command does not cleanly exit; openssl s_client actually acts as a client and leaves the connection open, waiting for input. If you want it to immediately exit (e.g. to parse the output in a shell script) simply pipe echo into it:

echo | openssl s_client -connect website.example:443 | openssl x509 -noout -text | grep DNS:

How do I get the SAN directly from a file?

For this, you don't need the openssl s_client command. Just add -in MyCertificate.crt on the openssl x509 command and once again pipe through grep, e.g.:

openssl x509 -noout -text -in MyCertificate.crt | grep DNS:
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
JoeSlav
  • 4,479
  • 4
  • 31
  • 50
  • I think you can also accept your own answer. That way it won't appear as an "open question" to others :) – alestanis Oct 29 '12 at 20:39
  • Oh ok. I have never accepted my own answers, didn't know there was a time limit :) – alestanis Oct 29 '12 at 20:43
  • It's the -text option which gives the full list of the cert including the SANs – Daniel Flippance Oct 10 '15 at 18:45
  • 2
    @JoeSlav I know it's five years too late, but I've submitted an edit that breaks down how your command works, and also explains how to modify it to read the data directly from a certificate file. Hope it helps :) – Doktor J Jun 16 '17 at 18:33
  • 1
    This works, but NOTE: You may need the parameter `-servername www.website.com` if these are virtual names to pick up on the correct SSL certificate. `echo | openssl s_client -servername www.website.com -connect sameserver.website.com:443 | openssl x509 -noout -text | grep DNS` – B. Shea Mar 29 '19 at 20:03
11

It's also possible to use the bundled OpenSSL functionality:

openssl s_client -connect website.example:443 </dev/null | openssl x509 -noout -ext subjectAltName
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
dAm2K
  • 9,923
  • 5
  • 44
  • 47
  • 1
    This doesn't work on CentOS 7 but does on Ubuntu 20.04, so presumably depends on the version of OpenSSL and libraries installed. – David Gardner Jul 21 '22 at 12:20
  • 1
    The `x509` `-ext` argument works with at least OpenSSL 1.1.1p. Note that some sites will need the SNI provided in the `s_client` request, via the `-servername` argument, i.e. `openssl s_client -connect website.example:443 -servername website.example – user310346 Aug 01 '22 at 07:36
10

If you just want to see the SANs, the grep DNS: is the obvious solution.

If you want to have a cleaner list to process further, you can use this Perl regex to extract just the names : @names=/\sDNS:([^\s,]+)/g

For example:

true | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -text \
| perl -l -0777 -ne '@names=/\bDNS:([^\s,]+)/g; print join("\n", sort @names);'

Which would output this:

example.com
example.edu
example.net
example.org
www.example.com
www.example.edu
www.example.net
www.example.org

So you could pipe that to while read name; do echo "do stuff with $name"; done etc.

Or for a comma-separated list on one line, replace join("\n", with join(",",

(The -0777 switch for perl makes it read the whole input at once instead of line by line)

mivk
  • 13,452
  • 5
  • 76
  • 69
5

Is there a way to programmatically check the Alternative Names of a SAN SSL cert?

There could be multiple SANs in a X509 certificate. The following is from the OpenSSL wiki at SSL/TLS Client. It loops over the names and prints them.

You get the X509* from a function like SSL_get_peer_certificate from a TLS connection, d2i_X509 from memory or PEM_read_bio_X509 from the filesystem.

void print_san_name(const char* label, X509* const cert)
{
    int success = 0;
    GENERAL_NAMES* names = NULL;
    unsigned char* utf8 = NULL;

    do
    {
        if(!cert) break; /* failed */

        names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
        if(!names) break;

        int i = 0, count = sk_GENERAL_NAME_num(names);
        if(!count) break; /* failed */

        for( i = 0; i < count; ++i )
        {
            GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
            if(!entry) continue;

            if(GEN_DNS == entry->type)
            {
                int len1 = 0, len2 = -1;

                len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
                if(utf8) {
                    len2 = (int)strlen((const char*)utf8);
                }

                if(len1 != len2) {
                    fprintf(stderr, "  Strlen and ASN1_STRING size do not match (embedded null?): %d vs %d\n", len2, len1);
                }

                /* If there's a problem with string lengths, then     */
                /* we skip the candidate and move on to the next.     */
                /* Another policy would be to fails since it probably */
                /* indicates the client is under attack.              */
                if(utf8 && len1 && len2 && (len1 == len2)) {
                    fprintf(stdout, "  %s: %s\n", label, utf8);
                    success = 1;
                }

                if(utf8) {
                    OPENSSL_free(utf8), utf8 = NULL;
                }
            }
            else
            {
                fprintf(stderr, "  Unknown GENERAL_NAME type: %d\n", entry->type);
            }
        }

    } while (0);

    if(names)
        GENERAL_NAMES_free(names);

    if(utf8)
        OPENSSL_free(utf8);

    if(!success)
        fprintf(stdout, "  %s: <not available>\n", label);

}
jww
  • 97,681
  • 90
  • 411
  • 885
2

as someone would get just list of SANs – one per line:

openssl x509 -noout -text -in "${CERT_FILE}" | grep -Po 'DNS:\K[^,]+'

Bests,