Check expiry date of ssl certificate for multiple remote servers
I had the same issue and wrote this... It's quick and dirty, but should work. It'll log (and print to screen with debugging on) any certs which aren't yet valid or expire in the next 90 days. Might contain some bugs, but feel free to tidy it up.
#!/bin/sh
DEBUG=false
warning_days=90 # Number of days to warn about soon-to-expire certs
certs_to_check='serverA.test.co.uk:443
serverB.test.co.uk:8140
serverC.test.co.uk:443'
for CERT in $certs_to_check
do
$DEBUG && echo "Checking cert: [$CERT]"
output=$(echo | openssl s_client -connect ${CERT} 2>/dev/null |\
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' |\
openssl x509 -noout -subject -dates 2>/dev/null)
if [ "$?" -ne 0 ]; then
$DEBUG && echo "Error connecting to host for cert [$CERT]"
logger -p local6.warn "Error connecting to host for cert [$CERT]"
continue
fi
start_date=$(echo $output | sed 's/.*notBefore=\(.*\).*not.*/\1/g')
end_date=$(echo $output | sed 's/.*notAfter=\(.*\)$/\1/g')
start_epoch=$(date +%s -d "$start_date")
end_epoch=$(date +%s -d "$end_date")
epoch_now=$(date +%s)
if [ "$start_epoch" -gt "$epoch_now" ]; then
$DEBUG && echo "Certificate for [$CERT] is not yet valid"
logger -p local6.warn "Certificate for $CERT is not yet valid"
fi
seconds_to_expire=$(($end_epoch - $epoch_now))
days_to_expire=$(($seconds_to_expire / 86400))
$DEBUG && echo "Days to expiry: ($days_to_expire)"
warning_seconds=$((86400 * $warning_days))
if [ "$seconds_to_expire" -lt "$warning_seconds" ]; then
$DEBUG && echo "Cert [$CERT] is soon to expire ($seconds_to_expire seconds)"
logger -p local6.warn "cert [$CERT] is soon to expire ($seconds_to_expire seconds)"
fi
done
If using on OS X, you may find that the date
command doesn't work correctly. This is due to differences in the Unix and Linux version of this utility. The linked post has options for making this work.
Just run the command below and it will provide the expiration date:
echo q | openssl s_client -connect google.com.br:443 | openssl x509 -noout -enddate
You can use this command into a batch file, to gather this information for more remote servers.
Below is my script that as a check within nagios. It connects to a specific host, it verifies that the certificate is valid within a threshold set by the -c/-w options. It can check that the CN of the certificate matches the name you expect.
You need the python openssl library, and I did all the testing with python 2.7.
It would be trivial to have a shell script call this multiple times. The script returns the standard nagios exit values for critical/warning/ok status.
A simple check of Google's certificate can be performed like this.
./check_ssl_certificate -H www.google.com -p 443 -n www.google.com
Expire OK[108d] - CN OK - cn:www.google.com
check_ssl_certificate
#!/usr/bin/python
"""
Usage: check_ssl_certificate -H <host> -p <port> [-m <method>]
[-c <days>] [-w <days>]
-h show the help
-H <HOST> host/ip to check
-p <port> port number
-m <method> (SSLv2|SSLv3|SSLv23|TLSv1) defaults to SSLv23
-c <days> day threshold for critical
-w <days> day threshold for warning
-n name Check CN value is valid
"""
import getopt,sys
import __main__
from OpenSSL import SSL
import socket
import datetime
# On debian Based systems requires python-openssl
def get_options():
"get options"
options={'host':'',
'port':'',
'method':'SSLv23',
'critical':5,
'warning':15,
'cn':''}
try:
opts, args = getopt.getopt(sys.argv[1:], "hH:p:m:c:w:n:", ['help', "host", 'port', 'method'])
except getopt.GetoptError as err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
for o, a in opts:
if o in ("-h", "--help"):
print __main__.__doc__
sys.exit()
elif o in ("-H", "--host"):
options['host'] = a
pass
elif o in ("-p", "--port"):
options['port'] = a
elif o in ("-m", "--method"):
options['method'] = a
elif o == '-c':
options['critical'] = int(a)
elif o == '-w':
options['warning'] = int(a)
elif o == '-n':
options['cn'] = a
else:
assert False, "unhandled option"
if (''==options['host'] or
''==options['port']):
print __main__.__doc__
sys.exit()
if options['critical'] >= options['warning']:
print "Critical must be smaller then warning"
print __main__.__doc__
sys.exit()
return options
def main():
options = get_options()
# Initialize context
if options['method']=='SSLv3':
ctx = SSL.Context(SSL.SSLv3_METHOD)
elif options['method']=='SSLv2':
ctx = SSL.Context(SSL.SSLv2_METHOD)
elif options['method']=='SSLv23':
ctx = SSL.Context(SSL.SSLv23_METHOD)
else:
ctx = SSL.Context(SSL.TLSv1_METHOD)
# Set up client
sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
sock.connect((options['host'], int(options['port'])))
# Send an EOF
try:
sock.send("\x04")
sock.shutdown()
peer_cert=sock.get_peer_certificate()
sock.close()
except SSL.Error,e:
print e
exit_status=0
exit_message=[]
cur_date = datetime.datetime.utcnow()
cert_nbefore = datetime.datetime.strptime(peer_cert.get_notBefore(),'%Y%m%d%H%M%SZ')
cert_nafter = datetime.datetime.strptime(peer_cert.get_notAfter(),'%Y%m%d%H%M%SZ')
expire_days = int((cert_nafter - cur_date).days)
if cert_nbefore > cur_date:
if exit_status < 2:
exit_status = 2
exit_message.append('C: cert is not valid')
elif expire_days < 0:
if exit_status < 2:
exit_status = 2
exit_message.append('Expire critical (expired)')
elif options['critical'] > expire_days:
if exit_status < 2:
exit_status = 2
exit_message.append('Expire critical')
elif options['warning'] > expire_days:
if exit_status < 1:
exit_status = 1
exit_message.append('Expire warning')
else:
exit_message.append('Expire OK')
exit_message.append('['+str(expire_days)+'d]')
for part in peer_cert.get_subject().get_components():
if part[0]=='CN':
cert_cn=part[1]
if options['cn']!='' and options['cn'].lower()!=cert_cn.lower():
if exit_status < 2:
exit_status = 2
exit_message.append(' - CN mismatch')
else:
exit_message.append(' - CN OK')
exit_message.append(' - cn:'+cert_cn)
print ''.join(exit_message)
sys.exit(exit_status)
if __name__ == "__main__":
main()
get_pem
Connect to host:port, extract the certificate with sed and write it to /tmp/host.port.pem.
get_expiration_date
Read the given pem file and evaluate the notAfter key as a bash variable. Then print the file name and the date when it expires in a given locale.
get_pem_expiration_dates
Iterate some input file and run the above functions.
check.pems.sh
#!/bin/bash
get_pem () {
openssl s_client -connect $1:$2 < /dev/null |& \
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/w'/tmp/$1.$2.pem
}
get_expiration_date () {
local pemfile=$1 notAfter
if [ -s $pemfile ]; then
eval `
openssl x509 -noout -enddate -in /tmp/$pemfile |
sed -E 's/=(.*)/="\1"/'
`
printf "%40s: " $pemfile
LC_ALL=ru_RU.utf-8 date -d "$notAfter" +%c
else
printf "%40s: %s\n" $pemfile '???'
fi
}
get_pem_expiration_dates () {
local pemfile server port
while read host; do
pemfile=${host/ /.}.pem
server=${host% *}
port=${host#* }
if [ ! -f /tmp/$pemfile ]; then get_pem $server $port; fi
if [ -f /tmp/$pemfile ]; then get_expiration_date $pemfile; fi
done < ${1:-input.txt}
}
if [ -f "$1" ]; then
get_pem_expiration_dates "$1" ; fi
sample output
$ sh check.pems.sh input.txt
www.google.com.443.pem: Пн. 30 дек. 2013 01:00:00
superuser.com.443.pem: Чт. 13 марта 2014 13:00:00
slashdot.org.443.pem: Сб. 24 мая 2014 00:49:50
movielens.umn.edu.443.pem: ???
$ cat input.txt
www.google.com 443
superuser.com 443
slashdot.org 443
movielens.umn.edu 443
And to answer your question:
$ openssl s_client -connect www.google.com:443 </dev/null |& \
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | \
openssl x509 -noout -enddate |& \
grep ^notAfter