Shell script to check SSL certificate info like expiration date and subject

Remembering the correct openssl syntax for fetching certificate from a remote host or parsing a local certificate file for useful information is a chore, so I finally took my notes and combined into an easy to use shell script.

The script is mostly useful for not having to remember cryptic syntax, but in some cases will also parse and present information in a more readable format than the default openssl output.

It's also easy to add parsing and formatting as needed.

For example, to get certificate expiration date from a remote host, you would normally have to remember a command like:

$ echo | openssl s_client -connect gmail.com:443 2>/dev/null | openssl x509 -noout -enddate

And for your trouble, would be rewarded with output like this:

notAfter=Jun 10 00:00:00 2014 GMT

With my little script, you'd only have to type:

$ ssl-cert-info --host gmail.com --end

And get back output in your own timezone:

2014-06-09 17:00:00 PDT

However, if you really want to see raw output from openssl, you could pass any openssl option through to it like so (the option passed through in this case is '-dates'):

$ ssl-cert-info --host gmail.com --option -dates

And get back output like this:

notBefore=Mar 12 09:48:29 2014 GMT
notAfter=Jun 10 00:00:00 2014 GMT

Though I find the following much easier to understand:

$ ssl-cert-info --host gmail.com --dates
valid from: 2014-03-12 02:48:29 PDT
valid till: 2014-06-09 17:00:00 PDT

And here's the script:

#!/bin/bash
 
usage()
{
cat <<EOF
Usage: $(basename $0) [options]
 
This shell script is a simple wrapper around the openssl binary. It uses
s_client to get certificate information from remote hosts, or x509 for local
certificate files. It can parse out some of the openssl output or just dump all
of it as text.
 
Options:
 
  --all-info   Print all output, including boring things like Modulus and 
               Exponent.
 
  --alt        Print Subject Alternative Names. These will be typically be 
               additional hostnames that the certificate is valid for.
 
  --cn         Print commonName from Subject. This is typically the host for 
               which the certificate was issued.
 
  --debug      Print additional info that might be helpful when debugging this
               script.
 
  --end        Print certificate expiration date. For additional functionality
               related to certificate expiration, take a look at this script:
               "http://prefetch.net/code/ssl-cert-check".
 
  --dates      Print start and end dates of when the certificate is valid.
 
  --file       Use a local certificate file for input.
 
  --help       Print this help message.
 
  --host       Fetch the certificate from this remote host.
 
  --issuer     Print the certificate issuer.
 
  --most-info  Print almost everything. Skip boring things like Modulus and
               Exponent.
 
  --option     Pass any openssl option through to openssl to get its raw
               output.
 
  --port       Use this port when conneting to remote host. If ommitted, port
               defaults to 443.
 
  --subject    Print the certificate Subject -- typically address and org name.
 
Examples:
 
  1. Print a list of all hostnames that the certificate used by amazon.com 
     is valid for.
 
     $(basename $0) --host amazon.com --alt
     DNS:uedata.amazon.com
     DNS:amazon.com
     DNS:amzn.com
     DNS:www.amzn.com
     DNS:www.amazon.com
 
  2. Print issuer of certificate used by smtp.gmail.com. Fetch certficate info
     over port 465.
 
     $(basename $0) --host smtp.gmail.com --port 465 --issuer
     issuer= 
         countryName               = US
         organizationName          = Google Inc
         commonName                = Google Internet Authority G2
 
  3. Print valid dates for the certificate, using a local file as the source of 
     certificate data. Dates are formatted using the date command and display
     time in your local timezone instead of GMT.
 
     $(basename $0) --file /path/to/file.crt --dates
     valid from: 2014-02-04 16:00:00 PST
     valid till: 2017-02-04 15:59:59 PST
 
 
  4. Print certificate serial number. This script doesn't have a special option
     to parse out the serial number, so will use the generic --option flag to
     pass '-serial' through to openssl.
 
     $(basename $0) --host gmail.com --option -serial
     serial=4BF004B4DDC9C2F8
 
EOF
}
 
if ! [ -x "$(type -P openssl)" ]; then
  echo "ERROR: script requires openssl"
  echo "For Debian and friends, get it with 'apt-get install openssl'"
  exit 1
fi
 
while [ "$1" ]; do
  case "$1" in
        --file)
            shift
            crt="$1"
            source="local"
            ;;
        --host)
            shift
            host="$1"
            source="remote"
            ;;
        --port)
            shift
            port="$1"
            ;;
        --all-info)
            opt="-text"
            ;;
        --alt)
            FormatOutput() { 
              grep -A1 "Subject Alternative Name:" | tail -n1 |
              tr -d ' ' | tr ',' '\n'
              }
            ;;
        --cn)
            opt="-subject -nameopt multiline"
            FormatOutput() { 
              awk '/commonName/ {print$NF}'
              }
            ;;
        --dates)
            opt="-dates"
            FormatOutput() { 
              dates=$(cat -)
              start=$(grep Before <<<"$dates" | cut -d= -f2-)
              end=$(grep After <<<"$dates" | cut -d= -f2-)
              echo valid from: $(date -d "$start" '+%F %T %Z')
              echo valid till: $(date -d "$end" '+%F %T %Z')
              }
            ;;
        --end)
            opt="-enddate"
            FormatOutput() { 
              read end
              end=$(cut -d= -f2- <<<"$end")
              date -d "$end" '+%F %T %Z'
              }
            ;;
        --issuer)
            opt="-issuer -nameopt multiline"
            ;;
        --most-info)
            opt="-text -certopt no_header,no_version,no_serial,no_signame,no_pubkey,no_sigdump,no_aux"
            ;;
        --option)
            shift
            opt="$1"
            ;;
        --subject)
            opt="-subject -nameopt multiline"
            ;;
        --help)
            usage
            exit 0
            ;;
        --debug)
            DEBUG="yes"
            ;;
        *)
            echo "$(basename $0): invalid option $1" >&2
            echo "see --help for usage"
            exit 1
                  ;;
  esac
  shift
done
 
CheckLocalCert()
{ 
  openssl x509 -in $crt -noout $opt
}
 
CheckRemoteCert()
{
  echo | openssl s_client -connect $host:$port 2>/dev/null | openssl x509 -noout $opt
}
 
if [ -z "$(type -t FormatOutput)" ]; then
  FormatOutput() { cat; }
fi
 
if [ -z "$opt" ]; then
  opt="-text -certopt no_header,no_version,no_serial,no_signame,no_pubkey,no_sigdump,no_aux"
fi
 
if [ -z "$source" ]; then
  echo "ERROR: missing certificate source."
  echo "Provide one via '--file' or '--host' arguments."
  echo "See '--help' for examples." 
  exit 1
fi
 
if [ "$source" == "local" ]; then
  [ -n "$DEBUG" ] && echo "DEBUG: certificate source is local file"
  if [ -z "$crt" ]; then
    echo "ERROR: missing certificate file"
    exit 1
  fi
  [ -n "$DEBUG" ] && echo
  CheckLocalCert | FormatOutput
fi
 
if [ "$source" == "remote" ]; then
  [ -n "$DEBUG" ] && echo "DEBUG: certificate source is remote host"
  if [ -z "$host" ]; then
    echo "ERROR: missing remote host value."
    echo "Provide one via '--host' argument"
    exit 1
  fi
  if [ -z "$port" ]; then
    [ -n "$DEBUG" ] && echo "DEBUG: defaulting to 443 for port."
    port="443"
  fi
  [ -n "$DEBUG" ] && echo
  CheckRemoteCert | FormatOutput
fi

4 Comments

  • 1. n2 replies at 24th April 2014, 6:58 am :

    Thanks for the script! Will come real handy and beats having to memorise or alias openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -text

  • 2. luke replies at 13th August 2014, 6:59 am :

    Thanks.

  • 3. Indranil Das Gupta replies at 30th August 2014, 12:29 am :

    Hi Alain,

    I would like to use this bash script in one of my projects (which is incidentally on GPLv2+ licensing).

    How may I consider your script to be licensed as – Public Domain, BSD,
    MIT, GPLv2+ or AGPL?

    thanks in advance.
    -indra

  • 4. Alain Kelder replies at 30th August 2014, 4:51 pm :

    Hi Indra,

    Everything on this blog is CC BY 3.0 US: http://creativecommons.org/licenses/by/3.0/us/

    Regards,
    Alain

Leave a comment

NOTE: Enclose quotes in <blockquote></blockquote>. Enclose code in <pre lang="LANG"></pre> (where LANG is one of these).