#!/usr/bin/env bash # vim: set fenc=utf-8: # Copyright (c) 2013-2014, Rainer Müller # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Version 2014-08-20 # https://raim.codingfarm.de/blog/tag/ssl-cert-check/ # https://github.com/raimue/ssl-cert-check #### Usage #### # # ./ssl-cert-check # # This tool will warn you if any of the specified certificates expires in the # next days. # # The first parameter is the number of days to warn in advance for expiring # certificates. All following parameters are treated as certificate # specifications and can be in one of the following formats: # # - An absolute path to a x509 PEM certificate file # For example: # /etc/apache2/ssl/example.org.pem # # - A file:// URI # For example: # file:///etc/apache2/ssl/example.org.pem # # - A ssl://: URI # For example: # ssl://example.org:443 # # - A ://[:] URI, this is the same as ssl://:. # The real port number is usually looked up in /etc/services, note that # you often need the one with the 's' suffix, like "https", "imaps", etc. # For example: # https://example.org # imaps://example.org (same as ssl://example.org:993) # # - A +starttls://[:] URI # Use the STARTTLS command to start a in-protocol TLS session after # opening an unencrypted connection. The openssl s_client needs to # support this protocol. At time of this writing, the supported protocols # are "smtp", "pop3", "imap", "ftp" and "xmpp". # For example: # imap+starttls://example.org # smtp+starttls://example.org:587 # # Example for your crontab: # MAILTO=root # 6 6 * * * nobody /usr/local/bin/ssl-cert-check 30 /etc/apache2/ssl/*.crt /etc/ssl/certs/dovecot.pem https://localhost ssl://localhost:465 smtp+starttls://localhost:587 # #### #### #### # First parameter specifies if certificate expire in the next X days DAYS=$1 shift if [[ ! $DAYS =~ ^[0-9]+$ ]]; then echo "Error: missing parameter or invalid number" >&2 exit 3 fi if [ $BASH_VERSINFO -lt 4 ]; then echo "Error: this script requires bash >= 4.0" >&2 exit 3 fi # We need extended globbing shopt -s extglob exitcode=0 for cert in "$@"; do enddate="" # For ease of use, map any absolute path name to a file:// URL if [[ $cert =~ ^/(.*)$ ]]; then cert=file://$cert fi # Split URI into protocol and target if [[ $cert =~ ^(.*)://(.*)$ ]]; then proto=${BASH_REMATCH[1]} target=${BASH_REMATCH[2]} else echo "Error: invalid certificate specification: $cert" >&2 if [ $exitcode -lt 2 ]; then exitcode=2 fi continue fi port="" extra="" case $proto in file) enddate=$(openssl x509 -checkend $(( 86400 * $DAYS )) -enddate -in "$target") ;; !(ssl)) # Handle special protocol definition for STARTTLS if [[ $proto =~ ^(.*)\+starttls$ ]]; then proto=${BASH_REMATCH[1]} extra="-starttls $proto" fi # If no port was given, use the default for this protocol servername=$target if [[ ! $target =~ :[0-9]+$ ]]; then target+=:$proto else servername=${target%:*} fi # (intentional fallthrough) ;& ssl) # Retrieve certificate # Create new temporary file tempfile=$(mktemp -t ssl-cert-check.XXXXX) if [ -z "$tempfile" ]; then echo "Error: unable to create temporary file" >&2 if [ $exitcode -lt 2 ]; then exitcode=2 fi continue fi # Delete temporary file if shell exits during certificate check trap "rm -f $tempfile" EXIT cmd=(openssl s_client -connect "$target" -servername "$servername" $extra) certificate=$(echo | ${cmd[@]} 2>$tempfile \ | sed -n -e '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \ -e '/-----END CERTIFICATE-----/q') if [ "$certificate" == "" ]; then echo "Error: unable to check $cert" >&2 echo "Error: command was '${cmd[@]}', openssl error messages were:" >&2 sed 's/^/ /' $tempfile >&2 if [ $exitcode -lt 2 ]; then exitcode=2 fi continue else # Extract notAfter date of validity enddate=$(echo "$certificate" | openssl x509 -checkend $(( 86400 * $DAYS )) -enddate) fi rm -f $tempfile ;; esac if [[ $enddate =~ (.*)Certificate\ will\ expire ]]; then echo "==> Certificate $cert is about to expire soon:" echo -n " ${BASH_REMATCH[1]}" if [ $exitcode -lt 1 ]; then exitcode=1 fi fi done exit $exitcode