#!/bin/sh # # check_ssl_cert # # Checks an X.509 certificate: # - checks if the server is running and delivers a valid certificate # - checks if the CA matches a given pattern # - checks the validity # # See the INSTALL file for installation instructions # # Copyright (c) 2007-2012 ETH Zurich. # Copyright (c) 2007-2019 Matteo Corti # # This module is free software; you can redistribute it and/or modify it # under the terms of GNU general public license (gpl) version 3. # See the LICENSE file for details. ################################################################################ # Constants VERSION=1.82.0 SHORTNAME="SSL_CERT" VALID_ATTRIBUTES=",startdate,enddate,subject,issuer,serial,modulus,serial,hash,email,ocsp_uri,fingerprint," SIGNALS="HUP INT QUIT TERM ABRT" # return value for the creation of temporary files TEMPFILE="" ################################################################################ # Functions ################################################################################ # Prints usage information # Params # $1 error message (optional) usage() { if [ -n "$1" ] ; then echo "Error: $1" 1>&2 fi #### The following line is 80 characters long (helps to fit the help text in a standard terminal) ######-------------------------------------------------------------------------------- echo echo "Usage: check_ssl_cert -H host [OPTIONS]" echo echo "Arguments:" echo " -H,--host host server" echo echo "Options:" echo " -A,--noauth ignore authority warnings (expiration only)" echo " --altnames matches the pattern specified in -n with alternate" echo " names too" echo " -C,--clientcert path use client certificate to authenticate" echo " --clientpass phrase set passphrase for client certificate." echo " -c,--critical days minimum number of days a certificate has to be valid" echo " to issue a critical status" echo " --curl-bin path path of the curl binary to be used" echo " -d,--debug produces debugging output" echo " --ecdsa cipher selection: force ECDSA authentication" echo " -e,--email address pattern to match the email address contained in the" echo " certificate" echo " -f,--file file local file path (works with -H localhost only)" echo " with -f you can not only pass a x509 certificate file" echo " but also a certificate revocation list (CRL) to check" echo " the validity period" echo " --file-bin path path of the file binary to be used" echo " --fingerprint SHA1 pattern to match the SHA1-Fingerprint" echo " --force-perl-date force the usage of Perl for date computations" echo " --format FORMAT format output template on success, for example" echo " \"%SHORTNAME% OK %CN% from '%CA_ISSUER_MATCHED%'\"" echo " -h,--help,-? this help message" echo " --ignore-exp ignore expiration date" echo " --ignore-ocsp do not check revocation with OCSP" echo " --ignore-sig-alg do not check if the certificate was signed with SHA1" echo " or MD5" echo " --ignore-ssl-labs-cache Forces a new check by SSL Labs (see -L)" echo " -i,--issuer issuer pattern to match the issuer of the certificate" echo " --issuer-cert-cache dir directory where to store issuer certificates cache" echo " -K,--clientkey path use client certificate key to authenticate" echo " -L,--check-ssl-labs grade SSL Labs assessment" echo " (please check https://www.ssllabs.com/about/terms.html)" echo " --check-ssl-labs-warn-grade SSL-Labs grade on which to warn" echo " --long-output list append the specified comma separated (no spaces) list" echo " of attributes to the plugin output on additional lines" echo " Valid attributes are:" 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 (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" echo " --no_tls1_1 disable TLS version 1.1" echo " --no_tls1_2 disable TLS version 1.2" echo " -N,--host-cn match CN with the host name" echo " -o,--org org pattern to match the organization of the certificate" echo " --openssl path path of the openssl binary to be used" echo " -p,--port port TCP port" echo " -P,--protocol protocol use the specific protocol" echo " {http|smtp|pop3|pops3s|imap|imaps|ftp|xmpp|irc|ldap}" echo " http: default" echo " smtp,pop3,imap,ftp,ldap: switch to TLS" echo " -s,--selfsigned allows self-signed certificates" echo " --serial serialnum pattern to match the serial number" echo " --sni name sets the TLS SNI (Server Name Indication) extension" echo " in the ClientHello message to 'name'" echo " --ssl2 forces SSL version 2" echo " --ssl3 forces SSL version 3" echo " --require-ocsp-stapling require OCSP stapling" echo " --require-san require the presence of a Subject Alternative Name" echo " extension" echo " -r,--rootcert path root certificate or directory to be used for" echo " certificate validation" echo " --rootcert-dir path root directory to be used for certificate validation" echo " --rootcert-file path root certificate to be used for certificate validation" echo " --rsa cipher selection: force RSA authentication" echo " --temp dir directory where to store the temporary files" echo " --terse terse output" echo " -t,--timeout seconds timeout after the specified time" echo " (defaults to 15 seconds)" echo " --tls1 force TLS version 1" echo " --tls1_1 force TLS version 1.1" echo " --tls1_2 force TLS version 1.2" echo " --tls1_3 force TLS version 1.3" echo " -v,--verbose verbose output" echo " -V,--version version" echo " -w,--warning days minimum number of days a certificate has to be valid" echo " to issue a warning status" echo " --xmpphost name specifies the host for the 'to' attribute of the stream element" echo echo "Deprecated options:" echo " --days days minimum number of days a certificate has to be valid" echo " (see --critical and --warning)" echo " --ocsp check revocation via OCSP" echo " -S,--ssl version force SSL version (2,3)" echo " (see: --ssl2 or --ssl3)" echo echo "Report bugs to https://github.com/matteocorti/check_ssl_cert/issues" echo exit 3 } ################################################################################ # trap passing the signal name # see https://stackoverflow.com/questions/2175647/is-it-possible-to-detect-which-trap-signal-in-bash/2175751#2175751 trap_with_arg() { func="$1" ; shift for sig ; do # shellcheck disable=SC2064 trap "$func $sig" "$sig" done } ################################################################################ # Cleanup temporary files remove_temporary_files() { if [ -n "${DEBUG}" ] ; then echo "[DBG] cleaning up temporary files" # shellcheck disable=SC2086 echo $TEMPORARY_FILES | tr '\ ' '\n' | sed 's/^/[DBG] /' fi # shellcheck disable=SC2086 if [ -n "$TEMPORARY_FILES" ]; then rm -f $TEMPORARY_FILES fi } ################################################################################ # Cleanup when exiting cleanup() { SIGNAL=$1 if [ -n "${DEBUG}" ] ; then echo "[DBG] signal caught $SIGNAL" fi remove_temporary_files # shellcheck disable=SC2086 trap - $SIGNALS exit } create_temporary_file() { # create a temporary file TEMPFILE="$( mktemp -t "${0##*/}XXXXXX" 2> /dev/null )" if [ -z "${TEMPFILE}" ] || [ ! -w "${TEMPFILE}" ] ; then unknown 'temporary file creation failure.' fi if [ -n "${DEBUG}" ] ; then echo "[DBG] temporary file $TEMPFILE created" fi # add the file to the list of temporary files TEMPORARY_FILES="$TEMPORARY_FILES $TEMPFILE" } ################################################################################ # Exits with a critical message # Params # $1 error message critical() { if [ -n "${HOST}" ] ; then if [ -n "${SNI}" ] ; then tmp=" ${SNI}" else tmp=" ${HOST}" fi fi remove_temporary_files printf '%s CRITICAL%s: %s%s%s\n' "${SHORTNAME}" "${tmp}" "$1" "${PERFORMANCE_DATA}" "${LONG_OUTPUT}" exit 2 } ################################################################################ # Exits with a warning message # Param # $1 warning message warning() { if [ -n "${HOST}" ] ; then if [ -n "${SNI}" ] ; then tmp=" ${SNI}" else tmp=" ${HOST}" fi fi remove_temporary_files printf '%s WARN%s: %s%s%s\n' "${SHORTNAME}" "${tmp}" "$1" "${PERFORMANCE_DATA}" "${LONG_OUTPUT}" exit 1 } ################################################################################ # Exits with an 'unknown' status # Param # $1 message unknown() { if [ -n "${HOST}" ] ; then if [ -n "${SNI}" ] ; then tmp=" ${SNI}" else tmp=" ${HOST}" fi fi remove_temporary_files printf '%s UNKNOWN%s: %s\n' "${SHORTNAME}" "${tmp}" "$1" exit 3 } ################################################################################ # Executes command with a timeout # Params: # $1 timeout in seconds # $2 command # Returns 1 if timed out 0 otherwise exec_with_timeout() { time=$1 # start the command in a subshell to avoid problem with pipes # (spawn accepts one command) command="/bin/sh -c \"$2\"" if [ -n "${DEBUG}" ] ; then echo "[DBG] executing with timeout (${time}s): $2" fi if [ -n "${TIMEOUT_BIN}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] ${TIMEOUT_BIN} $time $command" fi eval "${TIMEOUT_BIN} $time $command" > /dev/null 2>&1 if [ $? -eq 137 ] ; then critical "Timeout after ${time} seconds" fi elif [ -n "${EXPECT}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] expect -c \"set echo \\\"-noecho\\\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }\"" fi expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }" RET=$? if [ -n "${DEBUG}" ] ; then echo "[DBG] expect returned ${RET}" fi if [ "${RET}" -eq 1 ] ; then critical "Timeout after ${time} seconds" fi else eval "${command}" fi } ################################################################################ # Checks if a given program is available and executable # Params # $1 program name # Returns 1 if the program exists and is executable check_required_prog() { PROG=$(command -v "$1" 2> /dev/null) if [ -z "$PROG" ] ; then critical "cannot find program: $1" fi if [ ! -x "$PROG" ] ; then critical "$PROG is not executable" fi } ################################################################################ # Converts SSL Labs grades to a numeric value # (see https://www.ssllabs.com/downloads/SSL_Server_Rating_Guide.pdf) # Params # $1 program name # Sets NUMERIC_SSL_LAB_GRADE convert_ssl_lab_grade() { GRADE="$1" unset NUMERIC_SSL_LAB_GRADE case "${GRADE}" in 'A+'|'a+') # Value not in documentation NUMERIC_SSL_LAB_GRADE=85 shift ;; A|a) NUMERIC_SSL_LAB_GRADE=80 shift ;; 'A-'|'a-') # Value not in documentation NUMERIC_SSL_LAB_GRADE=75 shift ;; B|b) NUMERIC_SSL_LAB_GRADE=65 shift ;; C|c) NUMERIC_SSL_LAB_GRADE=50 shift ;; D|d) NUMERIC_SSL_LAB_GRADE=35 shift ;; E|e) NUMERIC_SSL_LAB_GRADE=20 shift ;; F|f) NUMERIC_SSL_LAB_GRADE=0 shift ;; T|t) # No trust: value not in documentation NUMERIC_SSL_LAB_GRADE=0 shift ;; M|m) # Certificate name mismatch: value not in documentation NUMERIC_SSL_LAB_GRADE=0 shift ;; *) unknown "Connot convert SSL Lab grade ${GRADE}" ;; esac } ################################################################################ # Tries to fetch the certificate fetch_certificate() { RET=0 # IPv6 addresses need brackets in a URI if [ "${HOST}" != "${HOST#*[0-9].[0-9]}" ]; then if [ -n "${DEBUG}" ] ; then echo "[DBG] ${HOST} is an IPv4 address" fi elif [ "${HOST}" != "${HOST#*:[0-9a-fA-F]}" ]; then if [ -n "${DEBUG}" ] ; then echo "[DBG] ${HOST} is an IPv6 address" fi if [ -z "${HOST##*[*}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] ${HOST} is already specified with brakcets" fi else if [ -n "${DEBUG}" ] ; then echo "[DBG] adding brackets to ${HOST}" fi HOST="[${HOST}]" fi else if [ -n "${DEBUG}" ] ; then echo "[DBG] ${HOST} is not an IP address" fi fi # 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} ${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} ${SSL_AU} 2> ${ERROR} 1> ${CERT}" RET=$? ;; pop3|imap|ftp|ldap) 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=$? ;; pop3s|imaps) 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=$? ;; xmpp) exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -starttls ${PROTOCOL} -connect $HOST:$XMPPPORT ${XMPPHOST} -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} ${SSL_AU} 2> ${ERROR} 1> ${CERT}" RET=$? ;; *) unknown "Error: unsupported protocol ${PROTOCOL}" ;; esac elif [ -n "${FILE}" ] ; then 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 '${HTTP_REQUEST}' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -crlf -ign_eof -connect $HOST:$PORT ${SERVERNAME} -showcerts -verify 6 ${ROOT_CA} ${SSL_VERSION} ${SSL_VERSION_DISABLED} ${SSL_AU} 2> ${ERROR} 1> ${CERT}" RET=$? fi if [ -n "${DEBUG}" ] ; then echo "[DBG] storing a copy of the retrieved certificate in ${HOST}.crt" cp "${CERT}" "${HOST}.crt" echo "[DBG] storing a copy of the OpenSSL errors in ${HOST}.error" cp "${ERROR}" "${HOST}.error" fi if [ "${RET}" -ne 0 ] ; then if [ -n "${DEBUG}" ] ; then sed 's/^/[DBG] SSL error: /' "${ERROR}" fi # s_client could verify the server certificate because the server requires a client certificate if grep -q '^Acceptable client certificate CA names' "${CERT}" ; then if [ -n "${VERBOSE}" ] ; then echo "The server requires a client certificate" fi else # Try to clean up the error message # Remove the 'verify and depth' lines # Take the 1st line (seems OK with the use cases I tested) ERROR_MESSAGE=$( grep -v '^depth' "${ERROR}" \ | grep -v '^verify' \ | head -n 1 ) critical "SSL error: ${ERROR_MESSAGE}" fi fi } ################################################################################ # 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 } ################################################################################ # Prepares sed-style command for variable replacement # Params # $1 variable name (e.g. SHORTNAME) # $2 variable value (e.g. SSL_CERT) var_for_sed() { echo "s|%$1%|$( echo "$2" | sed -e 's#|#\\\\|#g' )|g" } ################################################################################ # Main ################################################################################ main() { # Default values DEBUG="" OPENSSL="" FILE_BIN="" CURL_BIN="" IGNORE_SSL_LABS_CACHE="" PORT="443" XMPPPORT="5222" XMPPHOST="" SNI="" TIMEOUT="15" VERBOSE="" FORCE_PERL_DATE="" REQUIRE_SAN="" REQUIRE_OCSP_STAPLING="" OCSP="1" # enabled by default FORMAT="" # Set the default temp dir if not set if [ -z "${TMPDIR}" ] ; then TMPDIR="/tmp" fi ################################################################################ # Process command line options # # We do no use getopts since it is unable to process long options while true; do case "$1" in ######################################## # Options without arguments -A|--noauth) NOAUTH=1 shift ;; --altnames) ALTNAMES=1 shift ;; -d|--debug) DEBUG=1 VERBOSE=1 shift ;; -h|--help|-\?) usage exit 0 ;; --force-perl-date) FORCE_PERL_DATE=1 shift ;; --ignore-exp) NOEXP=1 shift ;; --ignore-sig-alg) NOSIGALG=1 shift ;; --ignore-ssl-labs-cache) IGNORE_SSL_LABS_CACHE="&startNew=on" shift ;; --no_ssl2) SSL_VERSION_DISABLED="${SSL_VERSION_DISABLED} -no_ssl2" shift ;; --no_ssl3) SSL_VERSION_DISABLED="${SSL_VERSION_DISABLED} -no_ssl3" shift ;; --no_tls1) SSL_VERSION_DISABLED="${SSL_VERSION_DISABLED} -no_tls1" shift ;; --no_tls1_1) SSL_VERSION_DISABLED="${SSL_VERSION_DISABLED} -no_tls1_1" shift ;; --no_tls1_2) SSL_VERSION_DISABLED="${SSL_VERSION_DISABLED} -no_tls1_2" shift ;; -N|--host-cn) COMMON_NAME="__HOST__" shift ;; -s|--selfsigned) SELFSIGNED=1 shift ;; --rsa) SSL_AU="-cipher aRSA" shift ;; --ecdsa) SSL_AU="-cipher aECDSA" shift ;; --ssl2) SSL_VERSION="-ssl2" shift ;; --ssl3) SSL_VERSION="-ssl3" shift ;; --tls1) SSL_VERSION="-tls1" shift ;; --tls1_1) SSL_VERSION="-tls1_1" shift ;; --tls1_2) SSL_VERSION="-tls1_2" shift ;; --tls1_3) SSL_VERSION="-tls1_3" shift ;; --ocsp) # deprecated shift ;; --ignore-ocsp) OCSP="" shift ;; --terse) TERSE=1 shift ;; -v|--verbose) VERBOSE=1 shift ;; -V|--version) echo "check_ssl_cert version ${VERSION}" exit 3 ;; ######################################## # Options with arguments -c|--critical) if [ $# -gt 1 ]; then CRITICAL="$2" shift 2 else unknown "-c,--critical requires an argument" fi ;; --curl-bin) if [ $# -gt 1 ]; then CURL_BIN="$2" shift 2 else unknown "--curl-bin requires an argument" fi ;; # Deprecated option: used to be as --warning --days) if [ $# -gt 1 ]; then WARNING="$2" shift 2 else unknown "-d,--days requires an argument" fi ;; -e|--email) if [ $# -gt 1 ]; then ADDR="$2" shift 2 else unknown "-e,--email requires an argument" fi ;; -f|--file) if [ $# -gt 1 ]; then FILE="$2" shift 2 else 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 ;; --format) if [ $# -gt 1 ]; then FORMAT="$2" shift 2 else unknown "-format requires an argument" fi ;; -H|--host) if [ $# -gt 1 ]; then HOST="$2" shift 2 else unknown "-H,--host requires an argument" fi ;; -i|--issuer) if [ $# -gt 1 ]; then ISSUER="$2" shift 2 else unknown "-i,--issuer requires an argument"s fi ;; --issuer-cert-cache) if [ $# -gt 1 ]; then ISSUER_CERT_CACHE="$2" shift 2 else unknown "--issuer-cert-cache requires an argument" fi ;; -L|--check-ssl-labs) if [ $# -gt 1 ]; then SSL_LAB_CRIT_ASSESSMENT="$2" shift 2 else unknown "-L|--check-ssl-labs requires an argument" fi ;; --check-ssl-labs-warn-grade) if [ $# -gt 1 ]; then SSL_LAB_WARN_ASSESTMENT="$2" shift 2 else unknown "--check-ssl-labs-warn-grade requires an argument" fi ;; --serial) if [ $# -gt 1 ]; then SERIAL_LOCK="$2" shift 2 else unknown "--serial requires an argument" fi ;; --fingerprint) if [ $# -gt 1 ]; then FINGERPRINT_LOCK="$2" shift 2 else unknown "--fingerprint requires an argument - SHA1 Fingerprint" fi ;; --long-output) if [ $# -gt 1 ]; then LONG_OUTPUT_ATTR="$2" shift 2 else unknown "--long-output requires an argument" fi ;; -n|--cn) if [ $# -gt 1 ]; then if [ -n "${COMMON_NAME}" ]; then COMMON_NAME="${COMMON_NAME} ${2}" else COMMON_NAME="${2}" fi shift 2 else unknown "-n,--cn requires an argument" fi ;; -o|--org) if [ $# -gt 1 ]; then ORGANIZATION="$2" shift 2 else unknown "-o,--org requires an argument" fi ;; --openssl) if [ $# -gt 1 ]; then OPENSSL="$2" shift 2 else unknown "--openssl requires an argument" fi ;; -p|--port) if [ $# -gt 1 ]; then PORT="$2" XMPPPORT="$2" shift 2 else unknown "-p,--port requires an argument" fi ;; -P|--protocol) if [ $# -gt 1 ]; then PROTOCOL="$2" shift 2 else unknown "-P,--protocol requires an argument" fi ;; -r|--rootcert) if [ $# -gt 1 ]; then ROOT_CA="$2" shift 2 else unknown "-r,--rootcert requires an argument" fi ;; --rootcert-dir) if [ $# -gt 1 ]; then ROOT_CA_DIR="$2" shift 2 else unknown "--rootcert-dir requires an argument" fi ;; --rootcert-file) if [ $# -gt 1 ]; then ROOT_CA_FILE="$2" shift 2 else unknown "--rootcert-file requires an argument" fi ;; -C|--clientcert) if [ $# -gt 1 ]; then CLIENT_CERT="$2" shift 2 else unknown "-c,--clientcert requires an argument" fi ;; -K|--clientkey) if [ $# -gt 1 ]; then CLIENT_KEY="$2" shift 2 else unknown "-K,--clientkey requires an argument" fi ;; --clientpass) if [ $# -gt 1 ]; then CLIENT_PASS="$2" shift 2 else unknown "--clientpass requires an argument" fi ;; --require-ocsp-stapling) REQUIRE_OCSP_STAPLING=1 shift ;; --require-san) REQUIRE_SAN=1 shift ;; --sni) if [ $# -gt 1 ]; then SNI="$2" shift 2 else unknown "--sni requires an argument" fi ;; -S|--ssl) if [ $# -gt 1 ]; then if [ "$2" = "2" ] || [ "$2" = "3" ] ; then SSL_VERSION="-ssl${2}" shift 2 else unknown "invalid argument for --ssl" fi else unknown "--ssl requires an argument" fi ;; -t|--timeout) if [ $# -gt 1 ]; then TIMEOUT="$2" shift 2 else unknown "-t,--timeout requires an argument" fi ;; --temp) if [ $# -gt 1 ] ; then # Override TMPDIR TMPDIR="$2" shift 2 else unknown "--temp requires an argument" fi ;; -w|--warning) if [ $# -gt 1 ]; then WARNING="$2" shift 2 else unknown "-w,--warning requires an argument" fi ;; --xmpphost) if [ $# -gt 1 ]; then XMPPHOST="$2" shift 2 else unknown "--xmpphost requires an argument" fi ;; ######################################## # Special --) shift break ;; -*) unknown "invalid option: ${1}" ;; *) if [ -n "$1" ] ; then unknown "invalid option: ${1}" fi break ;; esac done ################################################################################ # Set COMMON_NAME to hostname if -N was given as argument if [ "$COMMON_NAME" = "__HOST__" ] ; then COMMON_NAME="${HOST}" fi ################################################################################ # Sanity checks ############### # Check options if [ -z "${HOST}" ] ; then usage "No host specified" fi if [ -n "${ALTNAMES}" ] && [ -z "${COMMON_NAME}" ] ; then unknown "--altnames requires a common name to match (--cn or --host-cn)" fi if [ -n "${ROOT_CA}" ] ; then if [ ! -r "${ROOT_CA}" ] ; then unknown "Cannot read root certificate ${ROOT_CA}" fi if [ -d "${ROOT_CA}" ] ; then ROOT_CA="-CApath ${ROOT_CA}" elif [ -f "${ROOT_CA}" ] ; then ROOT_CA="-CAfile ${ROOT_CA}" else unknown "Root certificate of unknown type $(file "${ROOT_CA}" 2> /dev/null)" fi fi if [ -n "${ROOT_CA_DIR}" ] ; then if [ ! -d "${ROOT_CA_DIR}" ] ; then unknown "${ROOT_CA_DIR} is not a directory"; fi if [ ! -r "${ROOT_CA_DIR}" ] ; then unknown "Cannot read root directory ${ROOT_CA_DIR}" fi ROOT_CA_DIR="-CApath ${ROOT_CA_DIR}" fi if [ -n "${ROOT_CA_FILE}" ] ; then if [ ! -r "${ROOT_CA_FILE}" ] ; then unknown "Cannot read root certificate ${ROOT_CA_FILE}" fi ROOT_CA_FILE="-CAfile ${ROOT_CA_FILE}" fi if [ -n "${ROOT_CA_DIR}" ] || [ -n "${ROOT_CA_FILE}" ]; then ROOT_CA="${ROOT_CA_DIR} ${ROOT_CA_FILE}" fi if [ -n "${CLIENT_CERT}" ] ; then if [ ! -r "${CLIENT_CERT}" ] ; then unknown "Cannot read client certificate ${CLIENT_CERT}" fi fi if [ -n "${CLIENT_KEY}" ] ; then if [ ! -r "${CLIENT_KEY}" ] ; then unknown "Cannot read client certificate key ${CLIENT_KEY}" fi fi if [ -n "${CRITICAL}" ] ; then if ! echo "${CRITICAL}" | grep -q '^[0-9][0-9]*$' ; then unknown "invalid number of days ${CRITICAL}" fi fi if [ -n "${WARNING}" ] ; then if ! echo "${WARNING}" | grep -q '^[0-9][0-9]*$' ; then unknown "invalid number of days ${WARNING}" fi fi if [ -n "${CRITICAL}" ] && [ -n "${WARNING}" ] ; then if [ "${WARNING}" -le "${CRITICAL}" ] ; then unknown "--warning (${WARNING}) is less than or equal to --critical (${CRITICAL})" fi fi if [ -n "${TMPDIR}" ] ; then if [ ! -d "${TMPDIR}" ] ; then unknown "${TMPDIR} is not a directory"; fi if [ ! -w "${TMPDIR}" ] ; then unknown "${TMPDIR} is not writable"; fi fi if [ -n "${OPENSSL}" ] ; then if [ ! -x "${OPENSSL}" ] ; then unknown "${OPENSSL} ist not an executable" fi #if ! "${OPENSSL}" list-standard-commands | grep -q s_client ; then # unknown "${OPENSSL} ist not an openssl executable" #fi fi if [ -n "${SSL_LAB_CRIT_ASSESSMENT}" ] ; then convert_ssl_lab_grade "${SSL_LAB_CRIT_ASSESSMENT}" SSL_LAB_CRIT_ASSESSMENT_NUMERIC="${NUMERIC_SSL_LAB_GRADE}" fi if [ -n "${SSL_LAB_WARN_ASSESTMENT}" ] ; then convert_ssl_lab_grade "${SSL_LAB_WARN_ASSESTMENT}" SSL_LAB_WARN_ASSESTMENT_NUMERIC="${NUMERIC_SSL_LAB_GRADE}" if ( $SSL_LAB_WARN_ASSESTMENT_NUMERIC < $SSL_LAB_CRIT_ASSESSMENT_NUMERIC ); then unknown "--check-ssl-labs-warn-grade must be greater than -L|--check-ssl-labs" fi fi if [ -n "${DEBUG}" ] ; then echo "[DBG] ROOT_CA = ${ROOT_CA}" fi ####################### # Check needed programs # OpenSSL if [ -z "${OPENSSL}" ] ; then check_required_prog openssl OPENSSL=$PROG fi # file if [ -z "${FILE_BIN}" ] ; then check_required_prog file FILE_BIN=$PROG fi # curl if [ -z "${CURL_BIN}" ] ; then if [ -n "${SSL_LAB_CRIT_ASSESSMENT}" ] || [ -n "${OCSP}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] cURL binary needed. SSL Labs = ${SSL_LAB_CRIT_ASSESSMENT}, OCSP = ${OCSP}" echo "[DBG] cURL binary not specified" fi check_required_prog curl CURL_BIN=$PROG if [ -n "${DEBUG}" ] ; then echo "[DBG] cURL available: ${CURL_BIN}" fi else if [ -n "${DEBUG}" ] ; then echo "[DBG] cURL binary not needed. SSL Labs = ${SSL_LAB_CRIT_ASSESSMENT}, OCSP = ${OCSP}" fi fi fi # Expect (optional) EXPECT="$(command -v expect 2> /dev/null)" test -x "${EXPECT}" || EXPECT="" if [ -n "${VERBOSE}" ] ; then if [ -z "${EXPECT}" ] ; then echo "expect not available" else echo "expect available (${EXPECT})" fi fi # Timeout (optional) TIMEOUT_BIN="$(command -v timeout 2> /dev/null)" test -x "${TIMEOUT_BIN}" || TIMEOUT_BIN="" if [ -n "${VERBOSE}" ] ; then if [ -z "${TIMEOUT_BIN}" ] ; then echo "timeout not available" else echo "timeout available (${TIMEOUT_BIN})" fi fi if [ -z "${TIMEOUT_BIN}" ] && [ -z "${EXPECT}" ] && [ -n "${VERBOSE}" ] ; then echo "disabling timeouts" fi PERL="$(command -v perl 2> /dev/null)" if [ -n "${DEBUG}" ] && [ -n "${PERL}" ] ; then echo "[DBG] perl available: ${PERL}" fi DATEBIN="$(command -v 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" fi else 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 if [ -n "${FORCE_PERL_DATE}" ] ; then DATETYPE="PERL" fi if [ -n "${DEBUG}" ] ; then echo "[DBG] check_ssl_cert version: ${VERSION}" echo "[DBG] OpenSSL binary: ${OPENSSL}" echo "[DBG] OpenSSL version: $( ${OPENSSL} version )" OPENSSL_DIR="$( ${OPENSSL} version -d | sed -E 's/OPENSSLDIR: "([^"]*)"/\1/' )" echo "[DBG] OpenSSL configuration directory: ${OPENSSL_DIR}" DEFAULT_CA=0 if [ -f "${OPENSSL_DIR}"/cert.pem ] ; then DEFAULT_CA="$( grep -c BEGIN "${OPENSSL_DIR}"/cert.pem )" elif [ -f "${OPENSSL_DIR}"/certs ] ; then DEFAULT_CA="$( grep -c BEGIN "${OPENSSL_DIR}"/certs )" fi echo "[DBG] ${DEFAULT_CA} root certificates installed by default" echo "[DBG] System info: $( uname -a )" echo "[DBG] Date computation: ${DATETYPE}" fi ################################################################################ # Check if openssl s_client supports the -servername option # # openssl s_client now has a -help option, so we can use that. # Some older versions support -servername, but not -help # => We supply an invalid command line option to get the help # on standard error for these intermediate versions. # SERVERNAME= if ${OPENSSL} s_client -help 2>&1 | grep -q -- -servername || ${OPENSSL} s_client not_a_real_option 2>&1 | grep -q -- -servername; then if [ -n "${SNI}" ]; then SERVERNAME="-servername ${SNI}" else SERVERNAME="-servername ${HOST}" fi if [ -n "${DEBUG}" ] ; then echo "[DBG] '${OPENSSL} s_client' supports '-servername': using ${SERVERNAME}" fi else if [ -n "${VERBOSE}" ] ; then echo "'${OPENSSL} s_client' does not support '-servername': disabling virtual server support" fi fi ################################################################################ # Check if openssl s_client supports the -xmpphost option # if ${OPENSSL} s_client -help 2>&1 | grep -q -- -xmpphost ; then XMPPHOST="-xmpphost ${XMPPHOST:-$HOST}" if [ -n "${DEBUG}" ] ; then echo "[DBG] '${OPENSSL} s_client' supports '-xmpphost': using ${XMPPHOST}" fi else if [ -n "${XMPPHOST}" ] ; then unknown " s_client' does not support '-xmpphost'" fi XMPPHOST= if [ -n "${VERBOSE}" ] ; then echo "'${OPENSSL} s_client' does not support '-xmpphost': disabling 'to' attribute" fi fi ################################################################################ # check if openssl s_client supports the SSL TLS version if [ -n "${SSL_VERSION}" ] ; then if ! "${OPENSSL}" s_client -help 2>&1 | grep -q -- "${SSL_VERSION}" ; then unknown "OpenSSL does not support the ${SSL_VERSION} version" fi fi ################################################################################ # define the HTTP request string if [ -n "${SNI}" ]; then HOST_HEADER="${SNI}" else HOST_HEADER="${HOST}" fi HTTP_REQUEST="HEAD / HTTP/1.1\\nHost: ${HOST_HEADER}\\nUser-Agent: check_ssl_cert/${VERSION}\\nConnection: close\\n\\n" ################################################################################ # Fetch the X.509 certificate # Temporary storage for the certificate and the errors create_temporary_file; CERT=$TEMPFILE create_temporary_file; ERROR=$TEMPFILE if [ -n "${OCSP}" ] ; then create_temporary_file; ISSUER_CERT_TMP=$TEMPFILE create_temporary_file; ISSUER_CERT_TMP2=$TEMPFILE fi if [ -n "${REQUIRE_OCSP_STAPLING}" ] ; then create_temporary_file; OCSP_RESPONSE_TMP=$TEMPFILE fi if [ -n "${VERBOSE}" ] ; then echo "downloading certificate to ${TMPDIR}" fi CLIENT="" if [ -n "${CLIENT_CERT}" ] ; then CLIENT="-cert ${CLIENT_CERT}" fi if [ -n "${CLIENT_KEY}" ] ; then CLIENT="${CLIENT} -key ${CLIENT_KEY}" fi CLIENTPASS="" if [ -n "${CLIENT_PASS}" ] ; then CLIENTPASS="-pass pass:${CLIENT_PASS}" fi # Cleanup before program termination # Using named signals to be POSIX compliant # shellcheck disable=SC2086 trap_with_arg cleanup $SIGNALS fetch_certificate if grep -q 'sslv3\ alert\ unexpected\ message' "${ERROR}" ; then if [ -n "${SERVERNAME}" ] ; then # Some OpenSSL versions have problems with the -servername option # We try without if [ -n "${VERBOSE}" ] ; then echo "'${OPENSSL} s_client' returned an error: trying without '-servername'" fi SERVERNAME="" fetch_certificate fi if grep -q 'sslv3\ alert\ unexpected\ message' "${ERROR}" ; then critical "cannot fetch certificate: OpenSSL got an unexpected message" fi fi if grep -q "BEGIN X509 CRL" "${CERT}" ; then # we are dealing with a CRL file OPENSSL_COMMAND="crl" OPENSSL_PARAMS="-nameopt utf8,oneline,-esc_msb" OPENSSL_ENDDATE_OPTION="-nextupdate" else # look if we are dealing with a regular certificate file (x509) if ! grep -q "CERTIFICATE" "${CERT}" ; then if [ -n "${FILE}" ] ; then if [ -r "${FILE}" ] ; then if "${OPENSSL}" crl -in "${CERT}" -inform DER | grep -q "BEGIN X509 CRL" ; then if [ -n "${VERBOSE}" ] ; then echo "File is DER encoded CRL" fi OPENSSL_COMMAND="crl" OPENSSL_PARAMS="-inform DER -nameopt utf8,oneline,-esc_msb" OPENSSL_ENDDATE_OPTION="-nextupdate" else critical "'${FILE}' is not a valid certificate file" fi else critical "'${FILE}' is not readable" fi else # See # http://stackoverflow.com/questions/1251999/sed-how-can-i-replace-a-newline-n # # - create a branch label via :a # - the N command appends a newline and and the next line of the input # file to the pattern space # - if we are before the last line, branch to the created label $!ba # ($! means not to do it on the last line (as there should be one final newline)) # - finally the substitution replaces every newline with a space on # the pattern space ERROR_MESSAGE="$(sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/; /g' "${ERROR}")" if [ -n "${VERBOSE}" ] ; then echo "Error: ${ERROR_MESSAGE}" fi critical "No certificate returned" fi else # parameters for regular x509 certifcates OPENSSL_COMMAND="x509" OPENSSL_PARAMS="-nameopt utf8,oneline,-esc_msb" OPENSSL_ENDDATE_OPTION="-enddate" fi fi if [ -n "${VERBOSE}" ] ; then echo "parsing the ${OPENSSL_COMMAND} certificate file" fi ################################################################################ # Parse the X.509 certificate or crl # shellcheck disable=SC2086 DATE="$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" ${OPENSSL_ENDDATE_OPTION} -noout | sed -e "s/^notAfter=//" -e "s/^nextUpdate=//")" if [ ${OPENSSL_COMMAND} = "crl" ]; then CN="" SUBJECT="" SERIAL=0 OCSP_URI="" VALID_ATTRIBUTES=",lastupdate,nextupdate,issuer," # shellcheck disable=SC2086 ISSUERS="$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -issuer -noout)" else # we need to remove everything before 'CN = ', to remove an eventual email supplied with / and additional elements (after ', ') # shellcheck disable=SC2086 CN="$($OPENSSL x509 -in "${CERT}" -subject -noout ${OPENSSL_PARAMS} | sed -e "s/^.*[[:space:]]*CN[[:space:]]=[[:space:]]//" -e "s/\\/[[:alpha:]][[:alpha:]]*=.*\$//" -e "s/,.*//" )" # shellcheck disable=SC2086 SUBJECT="$($OPENSSL x509 -in "${CERT}" -subject -noout ${OPENSSL_PARAMS})" SERIAL="$($OPENSSL x509 -in "${CERT}" -serial -noout | sed -e "s/^serial=//")" FINGERPRINT="$($OPENSSL x509 -in "${CERT}" -fingerprint -sha1 -noout | sed -e "s/^SHA1 Fingerprint=//")" # TO DO: we just take the first result: a loop over all the hosts should # shellcheck disable=SC2086 OCSP_URI="$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -ocsp_uri -noout | head -n 1)" # count the certificates in the chain NUM_CERTIFICATES=$(grep -c -- "-BEGIN CERTIFICATE-" "${CERT}") # start with first certificate CERT_IN_CHAIN=1 # shellcheck disable=SC2086 while [ $CERT_IN_CHAIN -le $NUM_CERTIFICATES ]; do if [ -n "$ISSUERS" ]; then ISSUERS="$ISSUERS\\n" fi # shellcheck disable=SC2086 ISSUERS="$ISSUERS$(sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' "${CERT}" | \ awk -v n=$CERT_IN_CHAIN '/-BEGIN CERTIFICATE-/{l++} (l==n) {print}' | \ $OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -issuer -noout)" CERT_IN_CHAIN=$(( CERT_IN_CHAIN + 1 )) done fi # Handle properly openssl x509 -issuer -noout output format differences: # OpenSSL 1.1.0: issuer=C = XY, ST = Alpha, L = Bravo, O = Charlie, CN = Charlie SSL CA # OpenSSL 1.0.2: issuer= /C=XY/ST=Alpha/L=Bravo/O=Charlie/CN=Charlie SSL CA 3 # shellcheck disable=SC2086 ISSUERS=$(echo "$ISSUERS" | sed 's/\\n/\n/g' | sed -e "s/^.*\\/CN=//" -e "s/^.* CN = //" -e "s/^.*, O = //" -e "s/\\/[A-Za-z][A-Za-z]*=.*\$//" -e "s/, [A-Za-z][A-Za-z]* =.*\$//") # we just consider the first URI # TODO check SC2016 # shellcheck disable=SC2086,SC2016 ISSUER_URI="$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -text -noout | grep "CA Issuers" | head -n 1 | sed -e "s/^.*CA Issuers - URI://" | tr -d '"!|;$(){}<>`&')" # TODO: should be checked # shellcheck disable=SC2021 if [ -z "${ISSUER_URI}" ] ; then if [ -n "${VERBOSE}" ] ; then echo "cannot find the CA Issuers in the certificate: disabling OCSP checks" fi OCSP="" elif [ "${ISSUER_URI}" != "$(echo "${ISSUER_URI}" | tr -d '[[:space:]]')" ]; then if [ -n "${VERBOSE}" ] ; then echo "unable to fetch the CA issuer certificate (spaces in URI)" fi OCSP="" elif ! echo "${ISSUER_URI}" | grep -qi '^http' ; then if [ -n "${VERBOSE}" ] ; then echo "unable to fetch the CA issuer certificate (unsupported protocol)" fi OCSP="" fi # Check OCSP stapling if [ -n "${REQUIRE_OCSP_STAPLING}" ] ; then if [ -n "${VERBOSE}" ] ; then echo "checking OCSP stapling" fi exec_with_timeout "$TIMEOUT" "printf '${HTTP_REQUEST}' | openssl s_client -connect ${HOST}:${PORT} ${SERVERNAME} -status 2> /dev/null | grep -A 17 'OCSP response:' > $OCSP_RESPONSE_TMP" if [ -n "${DEBUG}" ] ; then sed 's/^/[DBG]\ /' "${OCSP_RESPONSE_TMP}" fi if ! grep -q 'Next Update' "${OCSP_RESPONSE_TMP}" ; then critical "OCSP stapling not enabled" else if [ -n "${VERBOSE}" ] ; then echo " OCSP stapling enabled" fi fi fi # shellcheck disable=SC2086 SIGNATURE_ALGORITHM="$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -text -noout | grep 'Signature Algorithm' | head -n 1)" if [ -n "${DEBUG}" ] ; then echo "[DBG] ${SUBJECT}" echo "[DBG] CN = ${CN}" # shellcheck disable=SC2162 echo "$ISSUERS" | while read LINE; do echo "[DBG] CA = ${LINE}" done echo "[DBG] SERIAL = ${SERIAL}" echo "[DBG] FINGERPRINT= ${FINGERPRINT}" echo "[DBG] OCSP_URI = ${OCSP_URI}" echo "[DBG] ISSUER_URI = ${ISSUER_URI}" echo "[DBG] ${SIGNATURE_ALGORITHM}" fi if echo "${SIGNATURE_ALGORITHM}" | grep -q "sha1" ; then if [ -n "${NOSIGALG}" ] ; then if [ -n "${VERBOSE}" ] ; then echo "${OPENSSL_COMMAND} Certificate is signed with SHA-1" fi else critical "${OPENSSL_COMMAND} Certificate is signed with SHA-1" fi fi if echo "${SIGNATURE_ALGORITHM}" | grep -qi "md5" ; then if [ -n "${NOSIGALG}" ] ; then if [ -n "${VERBOSE}" ] ; then echo "${OPENSSL_COMMAND} Certificate is signed with MD5" fi else critical "${OPENSSL_COMMAND} Certificate is signed with MD5" fi fi ################################################################################ # Generate the long output if [ -n "${LONG_OUTPUT_ATTR}" ] ; then check_attr() { ATTR="$1" if ! echo "${VALID_ATTRIBUTES}" | grep -q ",${ATTR}," ; then unknown "Invalid certificate attribute: ${ATTR}" else # shellcheck disable=SC2086 value="$(${OPENSSL} "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -noout -nameopt utf8,oneline,-esc_msb -"${ATTR}" | sed -e "s/.*=//")" LONG_OUTPUT="${LONG_OUTPUT}\\n${ATTR}: ${value}" fi } # Split on comma if [ "${LONG_OUTPUT_ATTR}" = "all" ] ; then LONG_OUTPUT_ATTR="${VALID_ATTRIBUTES}" fi attributes=$( echo ${LONG_OUTPUT_ATTR} | tr ',' "\\n" ) for attribute in $attributes ; do check_attr "${attribute}" done LONG_OUTPUT="$(echo "$LONG_OUTPUT" | sed 's/\\n/\n/g')" fi ################################################################################ # Compute for how many days the certificate will be valid if [ -n "${DATETYPE}" ]; then # shellcheck disable=SC2086 CERT_END_DATE=$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -noout ${OPENSSL_ENDDATE_OPTION} | sed -e "s/.*=//") OLDLANG=$LANG LANG=en_US if [ -n "${DEBUG}" ] ; then echo "[DBG] Date computations: ${DATETYPE}" fi case "${DATETYPE}" in "BSD") DAYS_VALID=$(( ( $(${DATEBIN} -jf "%b %d %T %Y %Z" "${CERT_END_DATE}" +%s) - $(${DATEBIN} +%s) ) / 86400 )) ;; "GNU") DAYS_VALID=$(( ( $(${DATEBIN} -d "${CERT_END_DATE}" +%s) - $(${DATEBIN} +%s) ) / 86400 )) ;; "PERL") # Warning: some shell script formatting tools will indent the EOF! (should be at position 0) if ! 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 ) ; then # somethig went wrong with the embedded Perl code: check the indentation of EOF unknown "Error computing the certificate validity with Perl" fi ;; esac LANG=$OLDLANG if [ -n "${VERBOSE}" ] ; then if [ "${DAYS_VALID}" -ge 0 ] ; then echo "The certificate will expire in ${DAYS_VALID} day(s)" else echo "The certificate expired "$((- DAYS_VALID))" day(s) ago" fi fi add_performance_data "days=$DAYS_VALID;${WARNING};${CRITICAL};;" fi ################################################################################ # Check the presence of a subjectAlternativeName (required for Chrome) # shellcheck disable=SC2086 SUBJECT_ALTERNATIVE_NAME=$($OPENSSL "${OPENSSL_COMMAND}" ${OPENSSL_PARAMS} -in "${CERT}" -text | grep --after-context=1 "509v3 Subject Alternative Name:" | tail -n 1 | sed -e "s/DNS://g" | sed -e "s/,//g" | sed -e "s/^\\ *//" ) if [ -n "${DEBUG}" ] ; then echo "[DBG] subjectAlternativeName = ${SUBJECT_ALTERNATIVE_NAME}" fi if [ -n "${REQUIRE_SAN}" ] && [ -z "${SUBJECT_ALTERNATIVE_NAME}" ] && [ ${OPENSSL_COMMAND} != "crl" ] ; then critical "The certificate for this site does not contain a Subject Alternative Name extension containing a domain name or IP address." fi ################################################################################ # Check the CN if [ -n "$COMMON_NAME" ] ; then ok="" if [ -n "${DEBUG}" ] ; then echo "[DBG] check CN: ${CN}" fi # 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 [ -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 [ -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 if echo "${COMMON_NAME}" | grep -q -i "^${CN}$" ; then ok="true" fi fi # Check alternate names if [ -n "${ALTNAMES}" ] && [ -z "$ok" ]; then for cn in ${COMMON_NAME} ; do ok="" if [ -n "${DEBUG}" ] ; then echo "[DBG] checking altnames against ${cn}" fi for alt_name in ${SUBJECT_ALTERNATIVE_NAME} ; do if [ -n "${DEBUG}" ] ; then echo "[DBG] check Altname: ${alt_name}" fi if echo "${alt_name}" | grep -q -i "^\\*\\." ; then # Match the domain if [ -n "${DEBUG}" ] ; then echo "[DBG] the altname ${alt_name} begins with a '*'" echo "[DBG] checking if the common name matches ^$(echo "${alt_name}" | cut -c 3-)\$" fi if echo "${cn}" | grep -q -i "^$(echo "${alt_name}" | cut -c 3-)\$" ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] the common name ${cn} matches ^$( echo "${alt_name}" | cut -c 3- )\$" fi ok="true" fi # Or the literal with the wildcard if [ -n "${DEBUG}" ] ; then echo "[DBG] checking if the common name matches ^$(echo "${alt_name}" | sed -e 's/[.]/[.]/g' -e 's/[*]/[A-Za-z0-9\-]*/' )\$" fi if echo "${cn}" | grep -q -i "^$(echo "${alt_name}" | sed -e 's/[.]/[.]/g' -e 's/[*]/[A-Za-z0-9\-]*/' )\$" ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] the common name ${cn} matches ^$(echo "${alt_name}" | 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 ^${alt_name}\$" fi if echo "${cn}" | grep -q -i "^${alt_name}\$" ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] the common name ${cn} matches ^${alt_name}\$" fi ok="true" fi else if echo "${cn}" | grep -q -i "^${alt_name}$" ; then ok="true" fi fi if [ -n "$ok" ] ; then #fail=$cn break; 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 if [ -n "${DEBUG}" ] ; then echo "[DBG] CN check finished" fi fi ################################################################################ # Check the issuer if [ -n "${ISSUER}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] check ISSUER: ${ISSUER}" fi ok="" CA_ISSUER_MATCHED=$(echo "${ISSUERS}" | grep "^${ISSUER}\$" | head -n1) if [ -n "${CA_ISSUER_MATCHED}" ]; then ok="true" else # this looks ugly but preserves spaces in CA name critical "invalid CA ('${ISSUER}' does not match '$(echo "${ISSUERS}" | tr '\n' '|' | sed "s/|\$//g" | sed "s/|/\\' or \\'/g")')" fi else CA_ISSUER_MATCHED="$(echo "${ISSUERS}" | head -n1)" fi ################################################################################ # Check the serial number if [ -n "${SERIAL_LOCK}" ] ; then ok="" if echo "${SERIAL}" | grep -q "^${SERIAL_LOCK}\$" ; then ok="true" fi if [ -z "$ok" ] ; then critical "invalid serial number ('${SERIAL}' does not match '${SERIAL_LOCK}')" fi fi ################################################################################ # Check the Fingerprint if [ -n "${FINGERPRINT_LOCK}" ] ; then ok="" if echo "${FINGERPRINT}" | grep -q "^${FINGERPRINT_LOCK}\$" ; then ok="true" fi if [ -z "$ok" ] ; then critical "invalid SHA1 Fingerprint ('${FINGERPRINT}' does not match '${FINGERPRINT_LOCK}')" fi fi ################################################################################ # Check the validity if [ -z "${NOEXP}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] Checking expiration date" fi if [ ${OPENSSL_COMMAND} = "x509" ]; then # x509 certificates (default) # We always check expired certificates if ! $OPENSSL x509 -in "${CERT}" -noout -checkend 0 > /dev/null ; then critical "${OPENSSL_COMMAND} certificate is expired (was valid until $DATE)" fi if [ -n "${CRITICAL}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] executing: $OPENSSL x509 -in ${CERT} -noout -checkend $(( CRITICAL * 86400 ))" fi if ! $OPENSSL x509 -in "${CERT}" -noout -checkend $(( CRITICAL * 86400 )) > /dev/null ; then critical "${OPENSSL_COMMAND} certificate will expire in ${DAYS_VALID} day(s) on $DATE" fi fi if [ -n "${WARNING}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] executing: $OPENSSL x509 -in ${CERT} -noout -checkend $(( WARNING * 86400 ))" fi if ! $OPENSSL x509 -in "${CERT}" -noout -checkend $(( WARNING * 86400 )) > /dev/null ; then warning "${OPENSSL_COMMAND} certificate will expire in ${DAYS_VALID} day(s) on $DATE" fi fi elif [ ${OPENSSL_COMMAND} = "crl" ]; then # CRL certificates # We always check expired certificates if [ "${DAYS_VALID}" -lt 1 ] ; then critical "${OPENSSL_COMMAND} certificate is expired (was valid until $DATE)" fi if [ -n "${CRITICAL}" ] ; then if [ "${DAYS_VALID}" -lt "${CRITICAL}" ] ; then critical "${OPENSSL_COMMAND} certificate will expire in ${DAYS_VALID} day(s) on $DATE" fi fi if [ -n "${WARNING}" ] ; then if [ "${DAYS_VALID}" -lt "${WARNING}" ] ; then warning "${OPENSSL_COMMAND} certificate will expire in ${DAYS_VALID} day(s) on $DATE" fi fi fi fi ################################################################################ # Check SSL Labs if [ -n "${SSL_LAB_CRIT_ASSESSMENT}" ] ; then if [ -n "${VERBOSE}" ] ; then echo "Checking SSL Labs assessment" fi while true; do if [ -n "${DEBUG}" ] ; then echo "[DBG] executing ${CURL_BIN} --silent \"https://api.ssllabs.com/api/v2/analyze?host=${HOST}${IGNORE_SSL_LABS_CACHE}\"" fi if [ -n "${SNI}" ] ; then JSON="$(${CURL_BIN} --silent "https://api.ssllabs.com/api/v2/analyze?host=${SNI}${IGNORE_SSL_LABS_CACHE}")" CURL_RETURN_CODE=$? else JSON="$(${CURL_BIN} --silent "https://api.ssllabs.com/api/v2/analyze?host=${HOST}${IGNORE_SSL_LABS_CACHE}")" CURL_RETURN_CODE=$? fi if [ ${CURL_RETURN_CODE} -ne 0 ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] curl returned ${CURL_RETURN_CODE}: ${CURL_BIN} --silent \"https://api.ssllabs.com/api/v2/analyze?host=${HOST}${IGNORE_SSL_LABS_CACHE}\"" fi unknown "Error checking SSL Labs: curl returned ${CURL_RETURN_CODE}, see 'man curl' for details" fi JSON="$(printf '%s' "${JSON}" | tr '\n' ' ' )" if [ -n "${DEBUG}" ] ; then echo "[DBG] Checking SSL Labs: ${CURL_BIN} --silent \"https://api.ssllabs.com/api/v2/analyze?host=${HOST}\"" echo "[DBG] SSL Labs JSON: ${JSON}" fi # We clear the cache only on the first run IGNORE_SSL_LABS_CACHE="" SSL_LABS_HOST_STATUS=$(echo "${JSON}" \ | sed 's/.*"status":[ ]*"\([^"]*\)".*/\1/') if [ -n "${DEBUG}" ] ; then echo "[DBG] SSL Labs status: ${SSL_LABS_HOST_STATUS}" fi case "${SSL_LABS_HOST_STATUS}" in 'ERROR') SSL_LABS_STATUS_MESSAGE=$(echo "${JSON}" \ | sed 's/.*"statusMessage":[ ]*"\([^"]*\)".*/\1/') critical "Error checking SSL Labs: ${SSL_LABS_STATUS_MESSAGE}" ;; 'READY') if ! echo "${JSON}" | grep -q "grade" ; then # Something went wrong SSL_LABS_STATUS_MESSAGE=$(echo "${JSON}" \ | sed 's/.*"statusMessage":[ ]*"\([^"]*\)".*/\1/') critical "SSL Labs error: ${SSL_LABS_STATUS_MESSAGE}" else SSL_LABS_HOST_GRADE=$(echo "${JSON}" \ | sed 's/.*"grade":[ ]*"\([^"]*\)".*/\1/') if [ -n "${DEBUG}" ] ; then echo "[DBG] SSL Labs grade: ${SSL_LABS_HOST_GRADE}" fi if [ -n "${VERBOSE}" ] ; then echo "SSL Labs grade: ${SSL_LABS_HOST_GRADE}" fi 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_CRIT_ASSESSMENT_NUMERIC}" # Check the grade if [ "${SSL_LABS_HOST_GRADE_NUMERIC}" -lt "${SSL_LAB_CRIT_ASSESSMENT_NUMERIC}" ] ; then critical "SSL Labs grade is ${SSL_LABS_HOST_GRADE} (instead of ${SSL_LAB_CRIT_ASSESSMENT})" elif [ -n "${SSL_LAB_WARN_ASSESTMENT_NUMERIC}" ]; then if [ "${SSL_LABS_HOST_GRADE_NUMERIC}" -lt "${SSL_LAB_WARN_ASSESTMENT_NUMERIC}" ] ; then warning "SSL Labs grade is ${SSL_LABS_HOST_GRADE} (instead of ${SSL_LAB_WARN_ASSESTMENT})" fi fi if [ -n "${DEBUG}" ] ; then echo "[DBG] SSL Labs grade (converted): ${SSL_LABS_HOST_GRADE_NUMERIC}" fi # We have a result: exit break fi ;; 'IN_PROGRESS') # Data not yet available: warn and continue if [ -n "${VERBOSE}" ] ; then echo "Warning: no cached data by SSL Labs, check initiated" fi ;; 'DNS') if [ -n "${VERBOSE}" ] ; then echo 'SSL Labs cannot resolve the domain name' fi ;; *) # Try to extract a message SSL_LABS_ERROR_MESSAGE=$(echo "${JSON}" \ | sed 's/.*"message":[ ]*"\([^"]*\)".*/\1/') if [ -z "${SSL_LABS_ERROR_MESSAGE}" ] ; then SSL_LABS_ERROR_MESSAGE="${JSON}" fi critical "Cannot check status on SSL Labs: ${SSL_LABS_ERROR_MESSAGE}" esac WAIT_TIME=60 if [ -n "${VERBOSE}" ] ; then echo "Waiting ${WAIT_TIME} seconds" fi sleep "${WAIT_TIME}" done fi ################################################################################ # Check revocation via OCSP if [ -n "${OCSP}" ]; then if [ -n "${DEBUG}" ] ; then echo "[DBG] Checking revokation via OCSP" fi ISSUER_HASH="$($OPENSSL x509 -in "${CERT}" -noout -issuer_hash)" if [ -z "${ISSUER_HASH}" ] ; then unknown 'unable to find issuer certificate hash.' fi if [ -n "${ISSUER_CERT_CACHE}" ] ; then if [ -r "${ISSUER_CERT_CACHE}/${ISSUER_HASH}.crt" ]; then if [ -n "${DEBUG}" ] ; then echo "[DBG] Found cached Issuer Certificate: ${ISSUER_CERT_CACHE}/${ISSUER_HASH}.crt" fi ISSUER_CERT="${ISSUER_CERT_CACHE}/${ISSUER_HASH}.crt" else if [ -n "${DEBUG}" ] ; then echo "[DBG] Not found cached Issuer Certificate: ${ISSUER_CERT_CACHE}/${ISSUER_HASH}.crt" fi fi fi if [ -z "${ISSUER_CERT}" ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] OCSP: fetching issuer certificate ${ISSUER_URI} to ${ISSUER_CERT_TMP}" fi exec_with_timeout "$TIMEOUT" "${CURL_BIN} --silent --location ${ISSUER_URI} > ${ISSUER_CERT_TMP}" if [ -n "${DEBUG}" ] ; then echo "[DBG] OCSP: issuer certificate type: $(${FILE_BIN} "${ISSUER_CERT_TMP}" | sed 's/.*://' )" fi # check the result if ! "${FILE_BIN}" "${ISSUER_CERT_TMP}" | grep -E -q ': (ASCII|PEM)' ; then if "${FILE_BIN}" "${ISSUER_CERT_TMP}" | grep -q ': data' ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] OCSP: converting issuer certificate from DER to PEM" fi cp "${ISSUER_CERT_TMP}" "${ISSUER_CERT_TMP2}" $OPENSSL x509 -inform DER -outform PEM -in "${ISSUER_CERT_TMP2}" -out "${ISSUER_CERT_TMP}" else unknown "Unable to fetch a valid certificate issuer intermediate certificate." fi fi if [ -n "${DEBUG}" ] ; then # 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_TMP}" "${FILE_NAME}" fi if [ -n "${ISSUER_CERT_CACHE}" ] ; then if [ ! -w "${ISSUER_CERT_CACHE}" ]; then unknown "Issuer certificates cache ${ISSUER_CERT_CACHE} is not writeable!" fi if [ -n "${DEBUG}" ] ; then echo "[DBG] Storing Issuer Certificate to cache: ${ISSUER_CERT_CACHE}/${ISSUER_HASH}.crt" fi cp "${ISSUER_CERT_TMP}" "${ISSUER_CERT_CACHE}/${ISSUER_HASH}.crt" fi ISSUER_CERT=${ISSUER_CERT_TMP} fi OCSP_HOST="$(echo "${OCSP_URI}" | sed -e "s@.*//\\([^/]\\+\\)\\(/.*\\)\\?\$@\\1@g" | sed 's/^http:\/\///' | sed 's/\/.*//' )" if [ -n "${DEBUG}" ] ; then echo "[DBG] OCSP: host = ${OCSP_HOST}" fi if [ -n "${OCSP_HOST}" ] ; then # check if -header is supported OCSP_HEADER="" # ocsp -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 | grep -q '^LibreSSL' || [ "$( ${OPENSSL} version | sed -e 's/OpenSSL \([0-9]\).*/\1/g' )" -gt 0 ] ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] openssl ocsp supports the -header option" fi # the -header option was first accepting key and value separated by space. The newer versions are using key=value KEYVALUE="" if openssl ocsp -help 2>&1 | grep header | grep -q 'key=value' ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] openssl ocsp -header requires 'key=value'" fi KEYVALUE=1 else if [ -n "${DEBUG}" ] ; then echo "[DBG] openssl ocsp -header requires 'key value'" fi fi # 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 if [ -n "${HTTP_PROXY:-}" ] ; then if [ -n "${KEYVALUE}" ] ; 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 )" else 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 )" fi else if [ -n "${KEYVALUE}" ] ; then 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 )" 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 )" fi fi if [ -n "${DEBUG}" ] ; then echo "${OCSP_RESP}" | sed 's/^/[DBG] OCSP: response = /' fi if echo "${OCSP_RESP}" | grep -qi "revoked" ; then if [ -n "${DEBUG}" ] ; then echo '[DBG] OCSP: revoked' fi critical "certificate is revoked" elif ! echo "${OCSP_RESP}" | grep -qi "good" ; then if [ -n "${DEBUG}" ] ; then echo "[DBG] OCSP: not good. HTTP_PROXY = ${HTTP_PROXY}" fi 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}\" \"${OCSP_HEADER}\" 2>&1" fi if [ -n "${OCSP_HEADER}" ] ; 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}" -host "${HTTP_PROXY#*://}" -path "${OCSP_URI}" 2>&1 )" fi else if [ -n "${DEBUG}" ] ; then echo "[DBG] executing $OPENSSL ocsp -no_nonce -issuer \"${ISSUER_CERT}\" -cert \"${CERT}\" -url \"${OCSP_URI}\" \"${OCSP_HEADER}\" 2>&1" fi if [ -n "${OCSP_HEADER}" ] ; then OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" "${OCSP_HEADER}" 2>&1 )" else OCSP_RESP="$($OPENSSL ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" 2>&1 )" fi fi critical "OCSP error: '${OCSP_RESP}'" fi else if [ -n "${VERBOSE}" ] ; then echo "openssl ocsp does not support the -header option: disabling OCSP checks" fi fi else if [ -n "${VERBOSE}" ] ; then echo "no OCSP host found: disabling OCSP checks" fi fi fi ################################################################################ # Check the organization if [ -n "$ORGANIZATION" ] ; then ORG=$($OPENSSL x509 -in "${CERT}" -subject -noout | sed -e "s/.*\\/O=//" -e "s/\\/.*//") if ! echo "$ORG" | grep -q "^$ORGANIZATION" ; then critical "invalid organization ('$ORGANIZATION' does not match '$ORG')" fi fi ################################################################################ # Check the organization if [ -n "$ADDR" ] ; then EMAIL=$($OPENSSL x509 -in "${CERT}" -email -noout) if [ -n "${VERBOSE}" ] ; then echo "checking email (${ADDR}): ${EMAIL}" fi if [ -z "${EMAIL}" ] ; then critical "the certificate does not contain an email address" fi if ! echo "$EMAIL" | grep -q "^$ADDR" ; then critical "invalid email ($ADDR does not match $EMAIL)" fi fi ################################################################################ # Check if the certificate was verified if [ -z "${NOAUTH}" ] && grep -q '^verify\ error:' "${ERROR}" ; then if grep -q '^verify\ error:num=[0-9][0-9]*:self\ signed\ certificate' "${ERROR}" ; then if [ -z "${SELFSIGNED}" ] ; then critical "Cannot verify certificate, self signed certificate" else SELFSIGNEDCERT="self signed " fi else if [ -n "${DEBUG}" ] ; then sed 's/^/[DBG] Error: /' "${ERROR}" fi # Process errors details=$( grep '^verify\ error:' "${ERROR}" | sed 's/verify\ error:num=[0-9]*://' | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/, /g' ) critical "Cannot verify certificate: ${details}" fi fi ################################################################################ # If we get this far, assume all is well. :) # If --altnames was specified or if the certificate is wildcard, # then we show the specified CN in addition to the certificate CN CHECKEDNAMES="" if [ -n "${ALTNAMES}" ] && [ -n "${COMMON_NAME}" ] && [ "${CN}" != "${COMMON_NAME}" ]; then CHECKEDNAMES="(${COMMON_NAME}) " elif [ -n "${COMMON_NAME}" ] && echo "${CN}" | grep -q -i "^\\*\\." ; then CHECKEDNAMES="(${COMMON_NAME}) " fi if [ -n "${DAYS_VALID}" ] ; then # nicer formatting if [ "${DAYS_VALID}" -gt 1 ] ; then DAYS_VALID=" (expires in ${DAYS_VALID} days)" elif [ "${DAYS_VALID}" -eq 1 ] ; then DAYS_VALID=" (expires tomorrow)" elif [ "${DAYS_VALID}" -eq 0 ] ; then DAYS_VALID=" (expires today)" elif [ "${DAYS_VALID}" -eq -1 ] ; then DAYS_VALID=" (expired yesterday)" else DAYS_VALID=" (expired ${DAYS_VALID} days ago)" fi fi if [ -n "${SSL_LABS_HOST_GRADE}" ] ; then SSL_LABS_HOST_GRADE=", SSL Labs grade: ${SSL_LABS_HOST_GRADE}" fi if [ -z "${CN}" ]; then DISPLAY_CN="" else DISPLAY_CN="'${CN}' " fi if [ -z "$FORMAT" ]; then if [ -n "${TERSE}" ]; then FORMAT="%SHORTNAME% OK %CN% %DAYS_VALID%" else FORMAT="%SHORTNAME% OK - %OPENSSL_COMMAND% %SELFSIGNEDCERT%certificate %DISPLAY_CN%%CHECKEDNAMES%from '%CA_ISSUER_MATCHED%' valid until %DATE%%DAYS_VALID%%SSL_LABS_HOST_GRADE%" fi fi if [ -n "${TERSE}" ]; then EXTRA_OUTPUT="${PERFORMANCE_DATA}" else EXTRA_OUTPUT="${LONG_OUTPUT}${PERFORMANCE_DATA}" fi echo "${FORMAT}${EXTRA_OUTPUT}" | sed \ -e "$( var_for_sed CA_ISSUER_MATCHED "${CA_ISSUER_MATCHED}" )" \ -e "$( var_for_sed CHECKEDNAMES "${CHECKEDNAMES}" )" \ -e "$( var_for_sed CN "${CN}" )" \ -e "$( var_for_sed DATE "${DATE}" )" \ -e "$( var_for_sed DAYS_VALID "${DAYS_VALID}" )" \ -e "$( var_for_sed DISPLAY_CN "${DISPLAY_CN}" )" \ -e "$( var_for_sed OPENSSL_COMMAND "${OPENSSL_COMMAND}" )" \ -e "$( var_for_sed SELFSIGNEDCERT "${SELFSIGNEDCERT}" )" \ -e "$( var_for_sed SHORTNAME "${SHORTNAME}" )" \ -e "$( var_for_sed SSL_LABS_HOST_GRADE "${SSL_LABS_HOST_GRADE}" )" remove_temporary_files exit 0 } if [ -z "${SOURCE_ONLY}" ]; then main "${@}" fi