Created
January 25, 2020 08:33
-
-
Save hongkongkiwi/b7670aaa44ed84eeb8e7886338967008 to your computer and use it in GitHub Desktop.
Bash script to check ssl certificate validity, also handles OCSP & CRL checking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
OPENSSL_BIN="openssl" | |
CERT_NAME="$1" | |
# CERT_CHAIN=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
# CRL_DER_NAME=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
if [ "$(uname)" == "Darwin" ]; then | |
DATE_BIN="gdate" | |
else | |
DATE_BIN="date" | |
fi | |
get_ocsp_url() { | |
if [ -f $1 ]; then | |
OCSP_URL=`"$OPENSSL_BIN" x509 -noout -ocsp_uri -in "$1"` | |
else | |
OCSP_URL=`"$OPENSSL_BIN" \ | |
x509 \ | |
-noout \ | |
-ocsp_uri \ | |
-connect $1` | |
fi | |
RET_CODE=$? | |
echo "$OCSP_URL" | |
return $RET_CODE | |
} | |
get_remote_cert() { | |
CERT=`"$OPENSSL_BIN" \ | |
s_client \ | |
-connect $1 \ | |
</dev/null 2>/dev/null \ | |
| sed -n '/-----BEGIN/,/-----END/p'` | |
RET_CODE=$? | |
[ -z "$CERT" ] && return $RET_CODE | |
[ $RET_CODE -ne 0 ] && return $RET_CODE | |
if [ -z $2 ]; then | |
CERT_NAME=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
echo "$CERT_NAME" | |
else | |
CERT_NAME=$2 | |
echo "$CERT" | |
fi | |
echo "$CERT" >$CERT_NAME | |
return $RET_CODE | |
} | |
extract_cert_chain() { | |
if [ -f $1 ]; then | |
CERT_CHAIN=`"$OPENSSL_BIN" \ | |
s_client \ | |
-showcerts \ | |
-in "$1" \ | |
</dev/null 2>/dev/null \ | |
| awk '/^-----BEGIN CERT/,/^-----END CERT/'` | |
else | |
CERT_CHAIN=`"$OPENSSL_BIN" \ | |
s_client \ | |
-showcerts \ | |
-connect $1 \ | |
</dev/null 2>/dev/null \ | |
| awk '/^-----BEGIN CERT/,/^-----END CERT/'` | |
fi | |
RET_CODE=$? | |
echo "$CERT_CHAIN" | |
[ ! -z $2 ] && echo "$CERT_CHAIN" >$2 | |
return $RET_CODE | |
} | |
get_crl_url() { | |
if [ -f $1 ]; then | |
CRL_URL=`"$OPENSSL_BIN" \ | |
x509 \ | |
-noout \ | |
-text \ | |
-in "$1" \ | |
</dev/null 2>/dev/null \ | |
| grep -A 4 'X509v3 CRL Distribution Points' \ | |
| grep -Eo '(http|https)://[^"]*'` | |
else | |
{ echo >&2 "ERROR: Cannot find certificate $1 to extract crl from"; exit 1; } | |
fi | |
RET_CODE=$? | |
echo "$CRL_URL" | |
return $RET_CODE | |
} | |
download_crl() { | |
CRL_DER_NAME=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
CRL_NAME=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
wget -q -O "$CRL_DER_NAME" "$1" | |
RET_CODE=$? | |
"$OPENSSL_BIN" crl -inform DER -in $CRL_DER_NAME -outform PEM -out $CRL_NAME >/dev/null < /dev/null | |
[ -f "$CRL_DER_NAME" ] && rm "$CRL_DER_NAME" | |
echo "$CRL_NAME" | |
return $RET_CODE | |
# openssl crl -inform DER -in "$CRL_DER_NAME" -outform PEM -out "$2" | |
# rm "$CRL_DER_NAME" | |
} | |
verify_crl() { | |
# SERIAL_NUMBER=`"$OPENSSL_BIN" x509 -in $1 -noout -serial` | |
CRL_CHAIN_NAME=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
cat "$1" "$2" > "$CRL_CHAIN_NAME" | |
#echo >&2 $("$OPENSSL_BIN" crl -inform DER -text -in "$CRL_DER_NAME" | grep "2") | |
#echo >&2 $(openssl crl -inform DER -in "$CRL_DER_NAME" -outform PEM) | |
#grep "$SERIAL_NUMBER" | |
"$OPENSSL_BIN" verify -crl_check -CAfile "$CRL_CHAIN_NAME" "$3" </dev/null 2>/dev/null | |
RET_CODE=$? | |
[ -f "$CRL_CHAIN_NAME" ] && rm "$CRL_CHAIN_NAME" | |
return $RET_CODE | |
} | |
check_expiry_date() { | |
CERT_EXPIRE=`"$OPENSSL_BIN" x509 -enddate -noout -in $1 | cut -d "=" -f2` | |
now_date=$("$DATE_BIN" +"%Y%m%d") # = 20130718 | |
expire_date=$("$DATE_BIN" -d "$CERT_EXPIRE" +"%Y%m%d") # = 20130715 | |
echo "$CERT_EXPIRE" | |
if [ $now_date -ge $expire_date ]; then | |
return 1 | |
else | |
return 0 | |
fi | |
} | |
check_issue_date() { | |
CERT_START=`"$OPENSSL_BIN" x509 -startdate -noout -in $1 | cut -d "=" -f2` | |
now_date=$("$DATE_BIN" +"%Y%m%d") # = 20130718 | |
start_date=$("$DATE_BIN" -d "$CERT_EXPIRE" +"%Y%m%d") # = 20130715 | |
echo "$CERT_START" | |
if [ $now_date -lt $start_date ]; then | |
return 1 | |
else | |
return 0 | |
fi | |
} | |
if [ ! -f "$CERT_NAME" ]; then | |
REMOTE_NAME=`echo "$CERT_NAME" | sed -e 's|^[^/]*//||' -e 's|/.*$||'` | |
REMOTE_PORT=`echo "$REMOTE_NAME" | cut -d ":" -f2` | |
if [ ! -z "$REMOTE_PORT" ] && [[ "$REMOTE_NAME" == "$REMOTE_PORT" ]]; then | |
REMOTE_NAME="$REMOTE_NAME:443" | |
elif [[ $REMOTE_PORT == "0" ]]; then | |
REMOTE_NAME="`echo "$REMOTE_NAME" | cut -d ":" -f1`:443" | |
fi | |
echo "Getting remote certificate from $REMOTE_NAME..." | |
CERT_NAME=`get_remote_cert "$REMOTE_NAME"` | |
if [ -z "$CERT_NAME" ]; then | |
{ echo >&2 "ERROR: Cannot extract certificate"; exit 1; } | |
else | |
echo " - Got remote certificate" | |
fi | |
echo "Getting remote certificate chain..." | |
CHAIN_NAME=$(mktemp /tmp/output.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } | |
RESULT=`extract_cert_chain "$REMOTE_NAME" "$CHAIN_NAME"` | |
if [ -z "$RESULT" ]; then | |
[ -f "$CHAIN_NAME" ] && rm "$CHAIN_NAME" | |
{ echo "Cannot extract certificate chain from certificate"; exit 1; } | |
else | |
echo " - Got remote certificate chain" | |
fi | |
else | |
exit 1 | |
fi | |
EXPIRY_DATE=`check_expiry_date "$CERT_NAME"` || { echo >&2 "ERROR: Certificate is expired!"; exit 1; } | |
START_DATE=`check_issue_date "$CERT_NAME"` || { echo >&2 "ERROR: Certificate start date is later than todays clock!!"; exit 1; } | |
echo "Extracting CRL url from certificate..." | |
CRL_URL=`get_crl_url "$CERT_NAME"` | |
if [ ! -z "$CRL_URL" ]; then | |
echo " - CRL URL: $CRL_URL" | |
echo "Downloading CRL list..." | |
CRL_NAME=`download_crl "$CRL_URL"` | |
echo " - Successfully downloaded" | |
echo "Verifying CRL to list" | |
verify_crl "$CERT_NAME" "$CHAIN_NAME" "$CRL_NAME" | |
echo " - Certificate not present on CRL list" | |
[ -f "$CRL_NAME" ] && rm "$CRL_NAME" | |
else | |
echo " - This certificate has no CRL URL - ignoring" | |
fi | |
echo "Extracting OCSP url from certificate..." | |
OCSP_URL=`get_ocsp_url "$CERT_NAME"` | |
if [ ! -z "$OCSP_URL" ]; then | |
echo " - OCSP URL: $OCSP_URL" | |
echo "Checking OCSP from $OCSP_URL ..." | |
# Useful info here https://raymii.org/s/articles/OpenSSL_Manually_Verify_a_certificate_against_an_OCSP.html | |
# Check OCSP of certificate | |
OCSP_STATUS=`"$OPENSSL_BIN" ocsp -issuer -no_nonce "$CHAIN_NAME" -cert "$CERT_NAME" -url "$OCSP_URL" 2>/dev/null` | |
TRIMMED_STATUS=`echo "$OCSP_STATUS" | sed 's/^[^:]*: //g'` | |
if [ "$TRIMMED_STATUS" == "ERROR: No Status found." ]; then | |
echo "Certificate is valid" | |
elif [ "$TRIMMED_STATUS" == "good" ]; then | |
echo "Certificate is valid" | |
elif [ "$TRIMMED_STATUS" == "revoked" ]; then | |
echo "Certificate is revoked" | |
exit 1 | |
elif [ "$TRIMMED_STATUS" == "unknown" ]; then | |
echo >&2 "OCSP cannot verify this certificate" | |
exit 3 | |
elif [ "$TRIMMED_STATUS" == "unauthorized" ]; then | |
echo >&2 "OCSP cannot verify this certificate" | |
exit 3 | |
else | |
echo >&2 "ERROR: We do not know how to handle this status!" | |
echo "$OCSP_STATUS" | |
OCSP_EXTENDED_STATUS=`"$OPENSSL_BIN" ocsp -issuer "$CHAIN_NAME" -cert "$CERT_NAME" -text -url "$OCSP_URL" 2>/dev/null` | |
echo >&2 "$OCSP_EXTENDED_STATUS" | |
exit 4 | |
fi | |
else | |
echo " - This certificate has no OCSP URL - ignoring" | |
fi | |
if [ -z $OCSP_URL ] && [ -z $CRL_URL ]; then | |
echo >&2 "This certificate has no OCSP URL nor CRL URL there is no way to verify validity." | |
exit 0 | |
fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment