From 24c56616bfae82a08ccad62e256a84952bd47ead Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Mon, 8 Nov 2021 23:01:33 +0000 Subject: Add support for checking IPv4 and IPv6 hosts separately, plus support for raw IPv6 addresses --- certexpiry.sh | 112 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/certexpiry.sh b/certexpiry.sh index 3901f95..468e4e0 100755 --- a/certexpiry.sh +++ b/certexpiry.sh @@ -4,21 +4,37 @@ 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 +# 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." - # Host before colon - 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") + # 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." @@ -39,36 +55,96 @@ while read -r HOSTANDPORT ; do # echo "DEBUG: PORT: $PORT." # echo "DEBUG: STARTTLS: $STARTTLS." - # Try various TLS versions against this host:port to try and get a response + # 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 - EXPIRYSTRING=$(echo "Q" | timeout "$TIMEOUT" openssl s_client $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 : //') + 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 -# echo "DEBUG: EXPIRYSTRING: $EXPIRYSTRING." + # 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." - # Convert expiry into unixtime - EXPIRY=$(date -d "$EXPIRYSTRING" +%s) -# echo "DEBUG: EXPIRY: $EXPIRY." NOW=$(date +%s) # echo "DEBUG: NOW: $NOW." - # Number of seconds left - DIFFERENCE=$(("$EXPIRY" - "$NOW")) -# echo "DEBUG: DIFFERENCE: $DIFFERENCE." + # 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 - if [ "$DIFFERENCE" -lt "$WARNSECONDS" ] ; then - echo "Warning! The certificate at $HOST:$PORT expires in $DIFFERENCE seconds (~$((DIFFERENCE / 60 / 60 / 24)) days)." + # 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 expires in $DIFFERENCE seconds (~$((DIFFERENCE / 60 / 60 / 24)) days)." +# echo "DEBUG: The certificate at $HOST:$PORT (IPv6) expires in $DIFFERENCE6 seconds (~$((DIFFERENCE6 / 60 / 60 / 24)) days)." fi done < "$HOSTSANDPORTS" -- cgit v1.2.3