check_ssl_cert: Update to 1.37.0

This commit is contained in:
Jan Wagner 2016-12-23 23:24:16 +01:00
parent 6af6c5412a
commit aee28d7a4f
2 changed files with 264 additions and 72 deletions

View file

@ -19,7 +19,7 @@
################################################################################
# Constants
VERSION=1.31.0
VERSION=1.37.0
SHORTNAME="SSL_CERT"
VALID_ATTRIBUTES=",startdate,enddate,subject,issuer,serial,modulus,serial,hash,email,ocsp_uri,fingerprint,"
@ -57,7 +57,9 @@ usage() {
echo " -d,--debug produces debugging output"
echo " -e,--email address pattern to match the email address contained in the"
echo " certificate"
echo " --ecdsa cipher selection: force ECDSA authentication"
echo " -f,--file file local file path (works with -H localhost only)"
echo " --file-bin path path of the file binary to be used"
echo " -h,--help,-? this help message"
echo " --ignore-exp ignore expiration date"
echo " --ignore-sig-alg do not check if the certificate was signed with SHA1"
@ -73,7 +75,8 @@ usage() {
echo " enddate, startdate, subject, issuer, modulus,"
echo " serial, hash, email, ocsp_uri and fingerprint."
echo " 'all' will include all the available attributes."
echo " -n,--cn name pattern to match the CN of the certificate"
echo " -n,--cn name pattern to match the CN of the certificate (can be"
echo " specified multiple times)"
echo " --no_ssl2 disable SSL version 2"
echo " --no_ssl3 disable SSL version 3"
echo " --no_tls1 disable TLS version 1"
@ -92,6 +95,7 @@ usage() {
echo " --ssl3 force SSL version 3"
echo " -r,--rootcert path root certificate or directory to be used for"
echo " certificate validation"
echo " --rsa cipher selection: force RSA authentication"
echo " -t,--timeout seconds timeout after the specified time"
echo " (defaults to 15 seconds)"
echo " --temp dir directory where to store the temporary files"
@ -110,7 +114,7 @@ usage() {
echo " -S,--ssl version force SSL version (2,3)"
echo " (see: --ss2 or --ssl3)"
echo
echo "Report bugs to: Matteo Corti <matteo@corti.li>"
echo "Report bugs to https://github.com/matteocorti/check_ssl_cert/issues"
echo
exit 3
@ -287,18 +291,23 @@ convert_ssl_lab_grade() {
fetch_certificate() {
RET=0
# Check if a protocol was specified (if not HTTP switch to TLS)
if [ -n "${PROTOCOL}" ] && [ "${PROTOCOL}" != "http" ] && [ "${PROTOCOL}" != "https" ] ; then
case "${PROTOCOL}" in
smtp)
exec_with_timeout "$TIMEOUT" "echo -e 'QUIT\r' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -starttls ${PROTOCOL} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} 2> ${ERROR} 1> ${CERT}"
exec_with_timeout "$TIMEOUT" "echo -e 'QUIT\r' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -starttls ${PROTOCOL} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} ${SSL_AU} 2> ${ERROR} 1> ${CERT}"
RET=$?
;;
irc)
exec_with_timeout "$TIMEOUT" "echo -e 'QUIT\r' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} 2> ${ERROR} 1> ${CERT}"
exec_with_timeout "$TIMEOUT" "echo -e 'QUIT\r' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} ${SSL_AU} 2> ${ERROR} 1> ${CERT}"
RET=$?
;;
pop3|imap|ftp|xmpp)
exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -starttls ${PROTOCOL} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} 2> ${ERROR} 1> ${CERT}"
exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -starttls ${PROTOCOL} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} ${SSL_AU} 2> ${ERROR} 1> ${CERT}"
RET=$?
;;
*)
unknown "Error: unsupported protocol ${PROTOCOL}"
@ -309,13 +318,15 @@ fetch_certificate() {
if [ "${HOST}" = "localhost" ] ; then
exec_with_timeout "$TIMEOUT" "/bin/cat '${FILE}' 2> ${ERROR} 1> ${CERT}"
RET=$?
else
unknown "Error: option 'file' works with -H localhost only"
fi
else
exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} 2> ${ERROR} 1> ${CERT}"
exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} ${SSL_AU} 2> ${ERROR} 1> ${CERT}"
RET=$?
fi
@ -326,7 +337,7 @@ fetch_certificate() {
cp "${ERROR}" "${HOST}.error"
fi
if [ $? -ne 0 ] ; then
if [ "${RET}" -ne 0 ] ; then
if [ -n "${DEBUG}" ] ; then
sed 's/^/[DBG] SSL error: /' "${ERROR}"
@ -357,6 +368,18 @@ fetch_certificate() {
}
################################################################################
# Adds metric to performance data
# Params
# $1 performance data in nagios plugin format,
# see https://nagios-plugins.org/doc/guidelines.html#AEN200
add_performance_data() {
if [ -z "${PERFORMANCE_DATA}" ]; then
PERFORMANCE_DATA="|${1}"
else
PERFORMANCE_DATA="${PERFORMANCE_DATA} $1"
fi
}
################################################################################
# Main
@ -366,6 +389,7 @@ main() {
# Default values
DEBUG=""
OPENSSL=""
FILE_BIN=""
IGNORE_SSL_LABS_CACHE=""
PORT="443"
TIMEOUT="15"
@ -444,6 +468,14 @@ main() {
SELFSIGNED=1
shift
;;
--rsa)
SSL_AU="-cipher aRSA"
shift
;;
--ecdsa)
SSL_AU="-cipher aECDSA"
shift
;;
--ssl2)
SSL_VERSION="-ssl2"
shift
@ -515,6 +547,14 @@ main() {
unknown "-f,--file requires an argument"
fi
;;
--file-bin)
if [ $# -gt 1 ]; then
FILE_BIN="$2"
shift 2
else
unknown "--file-bin requires an argument"
fi
;;
-H|--host)
if [ $# -gt 1 ]; then
HOST="$2"
@ -557,7 +597,11 @@ main() {
;;
-n|--cn)
if [ $# -gt 1 ]; then
COMMON_NAME="$2"
if [ -n "${COMMON_NAME}" ]; then
COMMON_NAME="${COMMON_NAME} ${2}"
else
COMMON_NAME="${2}"
fi
shift 2
else
unknown "-n,--cn requires an argument"
@ -785,6 +829,12 @@ main() {
OPENSSL=$PROG
fi
# file
if [ -z "${FILE_BIN}" ] ; then
check_required_prog file
FILE_BIN=$PROG
fi
# Expect (optional)
EXPECT="$(which expect 2> /dev/null)"
test -x "${EXPECT}" || EXPECT=""
@ -813,25 +863,57 @@ main() {
echo "disabling timeouts"
fi
# Perl with Date::Parse (optional)
PERL="$(which perl 2> /dev/null)"
test -x "${PERL}" || PERL=""
if [ -z "${PERL}" ] && [ -n "${VERBOSE}" ] ; then
echo "Perl not found: disabling date computations"
if [ -n "${DEBUG}" ] && [ -n "${PERL}" ] ; then
echo "[DBG] perl available: ${PERL}"
fi
if ! ${PERL} -e "use Date::Parse;" > /dev/null 2>&1 ; then
DATEBIN="$(which date 2> /dev/null)"
if [ -n "${DEBUG}" ] && [ -n "${DATEBIN}" ] ; then
echo "[DBG] date available: ${DATEBIN}"
fi
DATETYPE=""
if ! "${DATEBIN}" +%s >/dev/null 2>&1 ; then
# Perl with Date::Parse (optional)
test -x "${PERL}" || PERL=""
if [ -z "${PERL}" ] && [ -n "${VERBOSE}" ] ; then
echo "Perl not found: disabling date computations"
fi
if ! ${PERL} -e "use Date::Parse;" > /dev/null 2>&1 ; then
if [ -n "${VERBOSE}" ] ; then
echo "Perl module Date::Parse not installed: disabling date computations"
fi
PERL=""
else
if [ -n "${VERBOSE}" ] ; then
echo "Perl module Date::Parse installed: enabling date computations"
fi
DATETYPE="PERL"
if [ -n "${VERBOSE}" ] ; then
echo "Perl module Date::Parse not installed: disabling date computations"
fi
PERL=""
else
if [ -n "${VERBOSE}" ] ; then
echo "Perl module Date::Parse installed: enabling date computations"
fi
if $DATEBIN --version >/dev/null 2>&1 ; then
DATETYPE="GNU"
else
DATETYPE="BSD"
fi
if [ -n "${VERBOSE}" ] ; then
echo "found ${DATETYPE} date with timestamp support: enabling date computations"
fi
fi
@ -852,12 +934,17 @@ main() {
SERVERNAME=
if ${OPENSSL} s_client not_a_real_option 2>&1 | grep -q -- -servername ; then
if [ -n "${COMMON_NAME}" ] ; then
if [ -n "${COMMON_NAME}" ] && [ "${COMMON_NAME}" = "$(echo "${COMMON_NAME}" | tr -d ' ')" ] ; then
SERVERNAME="-servername ${COMMON_NAME}"
else
SERVERNAME="-servername ${HOST}"
fi
if [ -n "${DEBUG}" ] ; then
echo "[DBG] '${OPENSSL} s_client' supports '-servername': using ${SERVERNAME}"
fi
else
if [ -n "${VERBOSE}" ] ; then
@ -957,7 +1044,7 @@ main() {
fi
if [ -n "${VERBOSE}" ] ; then
echo "Parsing the certificate file"
echo "parsing the certificate file"
fi
################################################################################
@ -977,6 +1064,13 @@ main() {
ISSUER_URI="$($OPENSSL x509 -in "${CERT}" -text -noout | grep "CA Issuers" | sed -e "s/^.*CA Issuers - URI://")"
if [ -z "${ISSUER_URI}" ] ; then
if [ -n "${VERBOSE}" ] ; then
echo "cannot find the CA Issuers in the certificate: disabling OCSP checks"
fi
OCSP=""
fi
SIGNATURE_ALGORITHM="$($OPENSSL x509 -in "${CERT}" -text -noout | grep 'Signature Algorithm' | head -n 1)"
if [ -n "${DEBUG}" ] ; then
@ -1050,23 +1144,40 @@ main() {
################################################################################
# Compute for how many days the certificate will be valid
if [ -n "${PERL}" ] ; then
if [ -n "${DATETYPE}" ]; then
CERT_END_DATE=$($OPENSSL x509 -in "${CERT}" -noout -enddate | sed -e "s/.*=//")
DAYS_VALID=$(perl - "${CERT_END_DATE}" <<-"EOF"
use strict;
use warnings;
OLDLANG=$LANG
LANG=en_US
use Date::Parse;
if [ -n "${DEBUG}" ] ; then
echo "[DBG] Date computations: ${DATETYPE}"
fi
my $cert_date = str2time( $ARGV[0] );
case "${DATETYPE}" in
"BSD")
DAYS_VALID=$(( ( $(${DATEBIN} -jf "%b %d %T %Y %Z" "${CERT_END_DATE}" +%s) - $(${DATEBIN} +%s) ) / 86400 ))
;;
my $days = int (( $cert_date - time ) / 86400 + 0.5);
"GNU")
DAYS_VALID=$(( ( $(${DATEBIN} -d "${CERT_END_DATE}" +%s) - $(${DATEBIN} +%s) ) / 86400 ))
;;
print "$days\n";
EOF
)
"PERL")
DAYS_VALID=$(perl - "${CERT_END_DATE}" <<-"EOF"
use strict;
use warnings;
use Date::Parse;
my $cert_date = str2time( $ARGV[0] );
my $days = int (( $cert_date - time ) / 86400 + 0.5);
print "$days\n";
EOF
)
;;
esac
LANG=$OLDLANG
if [ -n "${VERBOSE}" ] ; then
@ -1077,7 +1188,7 @@ main() {
fi
fi
PERFORMANCE_DATA="|days=$DAYS_VALID;${WARNING};${CRITICAL};;"
add_performance_data "days=$DAYS_VALID;${WARNING};${CRITICAL};;"
fi
@ -1091,61 +1202,91 @@ main() {
echo "[DBG] check CN: ${CN}"
fi
if echo "${CN}" | grep -q "^\*\." ; then
# Common name is case insensitive: using grep for comparison (and not 'case' with 'shopt -s nocasematch' as not defined in POSIX
if echo "${CN}" | grep -q -i "^\*\." ; then
# Match the domain
if echo "${COMMON_NAME}" | grep -q "^$(echo "${CN}" | cut -c 3-)\$" ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] the common name ${CN} begins with a '*'"
echo "[DBG] checking if the common name matches ^$(echo "${CN}" | cut -c 3-)\$"
fi
if echo "${COMMON_NAME}" | grep -q -i "^$(echo "${CN}" | cut -c 3-)\$" ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] the common name ${COMMON_NAME} matches ^$( echo "${CN}" | cut -c 3- )\$"
fi
ok="true"
fi
# Or the literal with the wildcard
if echo "${COMMON_NAME}" | grep -q "^$(echo "${CN}" | sed -e 's/[.]/[.]/g' -e 's/[*]/[A-Za-z0-9\-]*/' )\$" ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] checking if the common name matches ^$(echo "${CN}" | sed -e 's/[.]/[.]/g' -e 's/[*]/[A-Za-z0-9\-]*/' )\$"
fi
if echo "${COMMON_NAME}" | grep -q -i "^$(echo "${CN}" | sed -e 's/[.]/[.]/g' -e 's/[*]/[A-Za-z0-9\-]*/' )\$" ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] the common name ${COMMON_NAME} matches ^$(echo "${CN}" | sed -e 's/[.]/[.]/g' -e 's/[*]/[A-Za-z0-9\-]*/' )\$"
fi
ok="true"
fi
# Or if both are exactly the same
if [ -n "${DEBUG}" ] ; then
echo "[DBG] checking if the common name matches ^${CN}\$"
fi
if echo "${COMMON_NAME}" | grep -q -i "^${CN}\$" ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] the common name ${COMMON_NAME} matches ^${CN}\$"
fi
ok="true"
fi
else
case "${COMMON_NAME}" in
${CN})
ok="true"
;;
esac
if echo "${COMMON_NAME}" | grep -q -i "^${CN}$" ; then
ok="true"
fi
fi
# Check alterante names
if [ -n "${ALTNAMES}" ] ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] checking altnames"
fi
for cn in ${COMMON_NAME} ; do
for alt_name in $($OPENSSL x509 -in "${CERT}" -text \
| grep --after-context=1 "509v3 Subject Alternative Name:" \
| tail -n 1 | sed -e "s/DNS://g" | sed -e "s/,//g") ; do
ok=""
if [ -n "${DEBUG}" ] ; then
echo "[DBG] ${alt_name}"
echo "[DBG] checking altnames against ${cn}"
fi
case "$COMMON_NAME" in
"$alt_name")
for alt_name in $($OPENSSL x509 -in "${CERT}" -text \
| grep --after-context=1 "509v3 Subject Alternative Name:" \
| tail -n 1 | sed -e "s/DNS://g" | sed -e "s/,//g") ; do
if [ -n "${DEBUG}" ] ; then
echo "[DBG] ${alt_name}"
fi
if echo "${cn}" | grep -q -i "^${alt_name}$" ; then
ok="true"
;;
esac
fi
done
if [ -z "$ok" ] ; then
fail=$cn
break;
fi
done
fi
if [ -n "$fail" ] ; then
critical "invalid CN ('$CN' does not match '$fail')"
fi
if [ -z "$ok" ] ; then
critical "invalid CN ('$CN' does not match '$COMMON_NAME')"
fi
@ -1308,6 +1449,8 @@ main() {
convert_ssl_lab_grade "${SSL_LABS_HOST_GRADE}"
SSL_LABS_HOST_GRADE_NUMERIC="${NUMERIC_SSL_LAB_GRADE}"
add_performance_data "ssllabs=${SSL_LABS_HOST_GRADE_NUMERIC}%;;${SSL_LAB_ASSESTMENT_NUMERIC}"
# Check the grade
if [ "${SSL_LABS_HOST_GRADE_NUMERIC}" -lt "${SSL_LAB_ASSESTMENT_NUMERIC}" ] ; then
critical "SSL Labs grade is ${SSL_LABS_HOST_GRADE} (instead of ${SSL_LAB_ASSESTMENT})"
@ -1366,18 +1509,41 @@ main() {
curl --silent "${ISSUER_URI}" > "${ISSUER_CERT}"
if file "${ISSUER_CERT}" | grep -q ': data' ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] OCSP: issuer certificate type: $(${FILE_BIN} "${ISSUER_CERT}" | sed 's/.*://' )"
fi
# check the result
if ! "${FILE_BIN}" "${ISSUER_CERT}" | grep -q ': (ASCII|PEM)' ; then
if "${FILE_BIN}" "${ISSUER_CERT}" | grep -q ': data' ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] OCSP: converting issuer certificate from DER to PEM"
fi
openssl x509 -inform DER -outform PEM -in "${ISSUER_CERT}" -out "${ISSUER_CERT}"
else
unknown "Unable to fetch OCSP issuer certificate."
if [ -n "${DEBUG}" ] ; then
echo "[DBG] OCSP: converting issuer certificate from DER to PEM"
fi
openssl x509 -inform DER -outform PEM -in "${ISSUER_CERT}" -out "${ISSUER_CERT}"
fi
if [ -n "${DEBUG}" ] ; then
echo "[DBG] OCSP: storing a copy of the retrieved issuer certificate to ${ISSUER_URI##*/}"
cp "${ISSUER_CERT}" "${ISSUER_URI##*/}"
# remove trailing /
FILE_NAME=${ISSUER_URI%/}
# remove everything up to the last slash
FILE_NAME=${FILE_NAME##*/}
echo "[DBG] OCSP: storing a copy of the retrieved issuer certificate to ${FILE_NAME}"
cp "${ISSUER_CERT}" "${FILE_NAME}"
fi
OCSP_HOST="$(echo "${OCSP_URI}" | sed -e "s@.*//\([^/]\+\)\(/.*\)\?\$@\1@g" | sed 's/^http:\/\///' | sed 's/\/.*//' )"
@ -1388,17 +1554,39 @@ main() {
# check if -header is supported
OCSP_HEADER=""
if "${OPENSSL}" ocsp 2>&1 | grep -q -- -header ; then
# oscp -header is supported in OpenSSL versions from 1.0.0, but not documented until 1.1.0
# so we check if the major version is greater than 0
if [ "$( ${OPENSSL} version | sed -e 's/OpenSSL \([0-9]\).*/\1/g' )" -gt 0 ]; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] openssl ocsp support the -header option"
fi
if [ -n "${DEBUG}" ] ; then
echo "[DBG] executing $OPENSSL ocsp -no_nonce -issuer ${ISSUER_CERT} -cert ${CERT} -url ${OCSP_URI} ${OCSP_HEADER} -header HOST ${OCSP_HOST}"
# http_proxy is sometimes lower- and sometimes uppercase. Programs usually check both
# shellcheck disable=SC2154
if [ -n "${http_proxy}" ] ; then
HTTP_PROXY="${http_proxy}"
fi
OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" -header HOST "${OCSP_HOST}" 2>&1 | grep -i "ssl_cert")"
if [ -n "${HTTP_PROXY:-}" ] ; then
if [ -n "${DEBUG}" ] ; then
echo "[DBG] executing $OPENSSL ocsp -no_nonce -issuer ${ISSUER_CERT} -cert ${CERT} -host ${HTTP_PROXY#*://} -path ${OCSP_URI} -header HOST ${OCSP_HOST}"
fi
OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -host "${HTTP_PROXY#*://}" -path "${OCSP_URI}" -header HOST "${OCSP_HOST}" 2>&1 | grep -i "ssl_cert")"
else
if [ -n "${DEBUG}" ] ; then
echo "[DBG] executing $OPENSSL ocsp -no_nonce -issuer ${ISSUER_CERT} -cert ${CERT} -url ${OCSP_URI} ${OCSP_HEADER} -header HOST ${OCSP_HOST}"
fi
OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" -header HOST "${OCSP_HOST}" 2>&1 | grep -i "ssl_cert")"
fi
if [ -n "${DEBUG}" ] ; then
echo "[DBG] OCSP: response = ${OCSP_RESP}"
@ -1408,7 +1596,11 @@ main() {
critical "certificate is revoked"
elif ! echo "${OCSP_RESP}" | grep -qi "good" ; then
OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" "${OCSP_HEADER}" 2>&1 )"
if [ -n "${HTTP_PROXY:-}" ] ; then
OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -host "${HTTP_PROXY#*://}" -path "${OCSP_URI}" "${OCSP_HEADER}" 2>&1 )"
else
OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" "${OCSP_HEADER}" 2>&1 )"
fi
critical "${OCSP_RESP}"
fi

View file

@ -1,7 +1,7 @@
Homepage: https://github.com/matteocorti/check_ssl_cert/blob/master/check_ssl_cert
Watch: https://raw.githubusercontent.com/matteocorti/check_ssl_cert/master/check_ssl_cert VERSION=([0-9.]+)
Recommends: ca-certificates, expect, libtimedate-perl, openssl
Version: 1.31.0
Version: 1.37.0
Uploaders: Jan Wagner <waja@cyconet.org>
Description: plugin checking an X.509 certificate
- checks if the server is running and delivers a valid certificate