#!/bin/bash set -euo pipefail # ==== Variables ==== # Output warning if fewer than this number of seconds until expiry WARNSECONDS=1209600 # Two weeks # File containing a newline separated list of host:port combinations to be checked, e.g.: # example.org # example.org:1234 # 1.2.3.4 # 1.2.3.4:1234 # [2001:12:34::] # [2001:12:34::]:1234 HOSTSANDPORTS="hostsandports.txt" # Number of seconds before OpenSSL should timeout when connecting to hosts TIMEOUT=10 # ==== Variables ==== IPV4REGEX="((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}" IPV6REGEX="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" # Loop through all host:port combinations while read -r HOSTANDPORT ; do # echo "DEBUG: HOSTANDPORT: $HOSTANDPORT." # Get the host and port # Is it an IPv6 address? if echo "$HOSTANDPORT" | grep -P "^\[$IPV6REGEX" ; then HOST=$(echo "$HOSTANDPORT" | grep -Eo "^\[.*?\]" | sed "s/\[//g ; s/\]//g") PORT=$(echo "$HOSTANDPORT" | sed "s/.*\]//" | cut -s -d ":" -f "2") # Let's assume it's an IPv4 address or a hostname else HOST=$(echo "$HOSTANDPORT" | cut -d ":" -f "1") # Port after colon, or nothing if no port given (assumes 443 later on) PORT=$(echo "$HOSTANDPORT" | cut -s -d ":" -f "2") fi # echo "DEBUG: HOST: $HOST." # Assume no STARTTLS (unless special ports later on) STARTTLS="" if [ "$PORT" = "" ] ; then # Assume 443 if no port specified PORT=443 elif [ "$PORT" -eq 25 ] ; then # Assume SMTP STARTTLS if port 25 STARTTLS="-starttls smtp" elif [ "$PORT" -eq 143 ] ; then # Assume IMAP STARTTLS if port 143 STARTTLS="-starttls imap" fi # echo "DEBUG: PORT: $PORT." # echo "DEBUG: STARTTLS: $STARTTLS." # Sanity check (IPv4) NOIPV4=0 # Does it have an A record? if [ $(dig A "$HOST" +short | wc -l) -eq 0 ] ; then NOIPV4=1 fi # Is it in fact an IPv4 address? if echo "$HOST" | grep -Pq "^$IPV4REGEX$" ; then NOIPV4=0 fi # Try various TLS versions against this host:port to try and get a response (IPv4) EXPIRYSTRING4=0 RETCODE=1 for PROTOCOL in -tls1_3 -tls1_2 -tls1_1 -tls1 ; do if [ "$NOIPV4" -eq 1 ] ; then break fi if [ "$RETCODE" -eq 0 ] ; then break fi set +e # Get the "Not After" field for the certificate expiry EXPIRYSTRING4=$(echo "Q" | timeout "$TIMEOUT" openssl s_client -4 $STARTTLS -connect "$HOST:$PORT" -servername "$HOST" "$PROTOCOL" 2> /dev/null | openssl x509 -noout -text 2> /dev/null | grep "Not After" | sed -r 's/\s*Not After : //') RETCODE="$?" set -e done # Sanity check (IPv6) NOIPV6=0 # Does it have a AAAA record? if [ $(dig AAAA "$HOST" +short | wc -l) -eq 0 ] ; then NOIPV6=1 fi # Is it in fact an IPv6 address? if echo "$HOST" | grep -Pq "^$IPV6REGEX$" ; then NOIPV6=0 fi # Try various TLS versions against this host:port to try and get a response (IPv6) EXPIRYSTRING6=0 RETCODE=1 for PROTOCOL in -tls1_3 -tls1_2 -tls1_1 -tls1 ; do if [ "$NOIPV6" -eq 1 ] ; then break fi if [ "$RETCODE" -eq 0 ] ; then break fi set +e # Get the "Not After" field for the certificate expiry EXPIRYSTRING6=$(echo "Q" | timeout "$TIMEOUT" openssl s_client -6 $STARTTLS -connect "[$HOST]:$PORT" -servername "$HOST" "$PROTOCOL" 2> /dev/null | openssl x509 -noout -text 2> /dev/null | grep "Not After" | sed -r 's/\s*Not After : //') RETCODE="$?" set -e done # echo "DEBUG: EXPIRYSTRING4: $EXPIRYSTRING4." # echo "DEBUG: EXPIRYSTRING6: $EXPIRYSTRING6." NOW=$(date +%s) # echo "DEBUG: NOW: $NOW." # Convert expiry into unixtime (IPv4) EXPIRY4=$(date -d "$EXPIRYSTRING4" +%s) # echo "DEBUG: EXPIRY4: $EXPIRY4." # Convert expiry into unixtime (IPv6) EXPIRY6=$(date -d "$EXPIRYSTRING6" +%s) # echo "DEBUG: EXPIRY6: $EXPIRY6." # Number of seconds left (IPv4) DIFFERENCE4=$((EXPIRY4 - NOW)) # echo "DEBUG: DIFFERENCE4: $DIFFERENCE4." # Number of seconds left (IPv6) DIFFERENCE6=$((EXPIRY6 - NOW)) # echo "DEBUG: DIFFERENCE6: $DIFFERENCE6." # Warn if less than WARNSECONDS less (IPv4) if [ "$DIFFERENCE4" -lt "$WARNSECONDS" ] && [ "$NOIPV4" -eq 0 ] ; then echo "Warning! The certificate at $HOST:$PORT (IPv4) expires in $DIFFERENCE4 seconds (~$((DIFFERENCE4 / 60 / 60 / 24)) days)." # else # echo "DEBUG: The certificate at $HOST:$PORT (IPv4) expires in $DIFFERENCE4 seconds (~$((DIFFERENCE4 / 60 / 60 / 24)) days)." fi # Warn if less than WARNSECONDS less (IPv6) if [ "$DIFFERENCE6" -lt "$WARNSECONDS" ] && [ "$NOIPV6" -eq 0 ] ; then echo "Warning! The certificate at $HOST:$PORT (IPv6) expires in $DIFFERENCE6 seconds (~$((DIFFERENCE6 / 60 / 60 / 24)) days)." # else # echo "DEBUG: The certificate at $HOST:$PORT (IPv6) expires in $DIFFERENCE6 seconds (~$((DIFFERENCE6 / 60 / 60 / 24)) days)." fi done < "$HOSTSANDPORTS"