Article Listing 1 Listing 2 sep2004.tar

Listing 2 ssl-cert-check.sh

#!/bin/bash
#
# Program: SSL Certificate Check <ssl-cert-check>
#
# Author: Ryan Matteson <matty@daemons.net>
#
# Current Version: 1.3
#
# Revision History:
#
#   Version 1.3
#      Updated the prints messages to display the reason a connection
#      failed (connection refused, connection timesout, bad cert, etc)
#
#      Updated the GNU date checking routines. I really wish UNIX/BSD/Linux
#      based systems utilized a common set of options/interfaces. *sigh*
#
#   Version 1.2
#      Added checks for each binary required
#      Added checks for connection timeouts
#
#   Versions 1.1
#      Added checks for GNU date
#      Added a "-h" option
#      Cleaned up the documentation
#
#  Version 1.0
#      Initial Release
#
# Last Updated: 8-20-2004
#
# Purpose: 
#  ssl-cert-check checks to see if a digital certificate in X.509 format
#  has expired. ssl-cert-check can be run in interactive and batch mode,
#  and provides facilities to alarm if a certificate is about to expire.
#
# License: 
#   This program is free software; you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the
#   Free Software Foundation; either version 2, or (at your option) any
#   later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Requirements:
#   Requires openssl and GNU date
# 
# Installation: 
#   Copy the shell script to a suitable location
# 
# Usage (script should be run as a non-privileged user): 
#   Usage: ./ssl-cert-check {{ [ -b ] && [ -f cert_file ] } || { [ -s common_name ] && [ -p port] }}
#              [ -e email ] [ -x expir_days ] [ -q ] [ -a ] [ -h ]
#     -a               : Send a warning message through email
#     -b               : Print the expiration date for all certificates in cert_file (batch mode)
#     -e email address : Email address to send expiration notices
#     -f cert file     : File with a list of common names and ports (eg., mail.daemons.net 443)
#     -h               : Print this screen
#     -p port          : Port to connect to (interactive mode)
#     -s commmon name  : Server to connect to (interactive mode)
#     -q               : Don't print anything on the console
#     -x days          : Certificate expiration interval (eg. if cert_date < days)
# 
# Examples:
#   Print the certificate expiration dates for a list of domains specified in the file ssldomains:
#     $ ssl-cert-check -b -f ssldomains
# 
#  Print the certificate expiration date for mail.daemons.net:
#     $ ssl-cert-check -s mail.daemons.net -p 995 
#
#  Run ssl-cert-check in quiet mode, check for all certificates that will
#  expire in 75-days or less. Email the expiring certs to matty@daemons.net:
#     $ ssl-cert-check -a -b -f ssldomains -q -x 75 -e matty@daemons.net
#

PATH=/bin:/usr/bin:/usr/local/bin:/usr/local/ssl/bin ; export PATH

# Who to page when an expired certificate is detected (cmdline: -e)
ADMIN="sysadmin@mydomain.com.1"

# Number of days to give as a buffer  (cmdline: -x)
WARNDAYS=30

# If QUIET is set to TRUE, don't print anything on the console (cmdline: -q)
QUIET="FALSE"

# Don't send emails by default (cmdline: -a)
ALARM="FALSE"

# Location of system binaries
DATE="/export/home/matty/bin/date"
MAIL="/bin/mail"
OPENSSL="/usr/local/ssl/bin/openssl"

# Place to stash temporary files
CERT_TMP="$HOME/cert.$$"
ERROR_TMP="$HOME/error.$$"

# Set the default umask to be somewhat restrictive
umask 077

# Converts a date in string format passed as $1 into
# the number of seconds since 01/01/70 00:00:00 UTC.
# Replace this with another date normalization routine if
# you do not have GNU date available.
utcseconds() {
        utcsec=`${DATE} -d "$1" +%s`
        echo "$utcsec"
}

# Calculate the number of seconds between two dates
date_diff() {
        diff=`expr $2 - $1`
        echo "${diff}"
}

# Calculate days given seconds
date_days() {
	DAYS=`expr ${1} \/ 86400`
	echo "${DAYS}"
}

# Method used to print a line with a certificates expiration status
prints() {
	if [ "${QUIET}" != "TRUE" ]
	then
		MIN_DATE=`echo $4 | awk '{ print $1, $2, $4 }'`
		printf "%-30s %-25s %-20s %-5s\n" "$1:$2" "$3" "$MIN_DATE" "$5"
	fi
}

# Print out a heading 
print_heading() {
	if [ "${QUIET}" != "TRUE" ]
	then
		printf "\n%-30s %-25s %-20s %-5s\n" "Host" "Status" "Expires" "Days Left"
	fi
}

# Provide a listing of how the tool works
useage() {
	echo "Usage: $0 {{ [ -b ] && [ -f cert_file ] } || { [ -s common_name ] && [ -p port] }}"
	echo "           [ -e email ] [ -x expir_days ] [ -q ] [ -a ] [ -h ]"
	echo "  -a               : Send a warning message through email "
	echo "  -b               : Print the expiration date for all certificates in cert_file (batch mode)"
	echo "  -e email address : Email address to send expiration notices"
	echo "  -f cert file     : File with a list of common names and ports (eg., blatch.com 443)"
	echo "  -h               : Print this screen"
	echo "  -p port          : Port to connect to (interactive mode)"
	echo "  -s commmon name  : Server to connect to (interactive mode)"
	echo "  -q               : Don't print anything on the console"
	echo "  -x days          : Certificate expiration interval (eg. if cert_date < days)"
}

# This method takes a HOST ($1) and PORT ($2) and checks to see
# if the certificate has expired
check_cert() {

	echo "" | ${OPENSSL} s_client -connect ${1}:${2} 2> ${ERROR_TMP} 1> ${CERT_TMP}

        if grep -i  "Connection refused" ${ERROR_TMP} > /dev/null
        then
                prints ${1} ${2} "Connection refused" "?"  "?"

        elif grep -i "gethostbyname failure" ${ERROR_TMP} > /dev/null
        then
                prints ${1} ${2} "Cannot resolve domain" "?"  "?"

        elif grep -i "Operation timed out" ${ERROR_TMP} > /dev/null
        then
                prints ${1} ${2} "Operation timed out" "?"  "?"

        elif grep -i "ssl handshake failure" ${ERROR_TMP} > /dev/null
        then
                prints ${1} ${2} "SSL handshake failed" "?"  "?"

	elif grep -i "connect: Connection timed out" ${ERROR_TMP} > /dev/null
        then
		prints ${1} ${2} "Connection timed out" "?"  "?"

        else
		# Get the expiration date with openssl
                CERTDATE=`${OPENSSL} x509 -in ${CERT_TMP} -enddate -noout | sed 's/notAfter\=//'`

		# Convert the date to seconds, and get the diff between NOW and the expiration date
                CERTUTC=`utcseconds "${CERTDATE}"`
                CERTDIFF=`date_diff ${NOWUTC} ${CERTUTC}`

                if [ ${CERTDIFF}  -lt 0 ]
                then
			if [ "${ALARM}" == "TRUE" ]
			then
                        	echo "The SSL certificate for ${1} has expired!" \
                        	| ${MAIL} -s "Certificate for ${1} has expired!" ${ADMIN}
			fi

			DAYS=`date_days $CERTDIFF`
                        prints ${1} ${2} "Expired" "$CERTDATE" "$DAYS"

                elif [ ${CERTDIFF} -lt ${WARNSECS} ]
                then
			if [ "${ALARM}" == "TRUE" ]
			then
                        	echo "The SSL certificate for ${1} will expire on ${CERTDATE}" \
                        	| ${MAIL} -s "$0: Certificate for ${1} will expire in ${WARNDAYS}-days or less" ${ADMIN}
			fi
			
			 DAYS=`date_days $CERTDIFF`
                         prints ${1} ${2} "Expiring" "${CERTDATE}" "${DAYS}"

                else
                        DAYS=`date_days $CERTDIFF`
                        prints ${1} ${2} "Valid" "${CERTDATE}"  "${DAYS}"

                fi
        fi
}

while getopts abe:f:hp:s:qx: option
do
        case "${option}"
        in
                a) ALARM="TRUE";;
                b) REPORT_ALL="TRUE";;
		e) ADMIN=${OPTARG};;
                f) SERVERFILE=$OPTARG;;
		h) useage
		   exit 1;;
                p) PORT=$OPTARG;;
		s) HOST=$OPTARG;;
		q) QUIET="TRUE";;
		x) WARNDAYS=$OPTARG;;
                \?) useage
		    exit 1;;
        esac
done


if [ ! -f ${OPENSSL} ]
then
	echo "ERROR: $OPENSSL does not exist. Please modify the \$OPENSSL variable."
	exit 1
fi

if [ ! -f ${DATE} ]
then
        echo "ERROR: $DATE does not exist. Please modify the \$DATE variable."
	exit 1
fi

if [ ! -f ${MAIL} ]
then
        echo "ERROR: $MAIL does not exist. Please modify the \$MAIL variable."
	exit 1
fi

if ${DATE} -R -d "Wed Jan 1 00:00:00 EDT 2000" "+%s" 2>&1 | grep -i "illegal option" > /dev/null
then
	echo "ERROR: \$DATE does not point to GNU date"
	exit 1
fi

if ${DATE} -R -d "Wed Jan 1 00:00:00 EDT 2000" "+%s" 2>&1 | grep -i "unknown option" > /dev/null
then
        echo "ERROR: \$DATE does not point to GNU date"
        exit 1
fi

# Baseline the dates so we have something to compare with
NOWDATE=`${DATE}`
NOWUTC=`utcseconds "${NOWDATE}"`

# Get the number of seconds to compare the UTC time with 
WARNSECS=`expr ${WARNDAYS} \* 86400`

# Touch the files to avoid issues
touch ${CERT_TMP} ${ERROR_TMP}

# IF a HOST and PORT were passed on the cmdline, use that
if [ "${HOST}" != "" ] && [ "${PORT}" != "" ]
then
	print_heading
	check_cert "${HOST}" "${PORT}"

# If a file and a "-a" are passed on the command line, check all
# of the certificates in the file to see if they are about to expire
elif [ "${REPORT_ALL}" == "TRUE" ] && [ -f "${SERVERFILE}" ]
then
	print_heading
	while read HOST PORT
	do
		check_cert "${HOST}" "${PORT}"

	done < ${SERVERFILE}

# There was an error, so print how the tool works
else
	useage
fi

# Remove the temporary files
rm -f ${CERT_TMP} ${ERROR_TMP}