git.lirion.de

Of git, get, and gud

summaryrefslogtreecommitdiffstats
path: root/nagios-plugins-contrib-24.20190301~bpo9+1/check_ssl_cert/check_ssl_cert-1.83.0/check_ssl_cert
diff options
context:
space:
mode:
Diffstat (limited to 'nagios-plugins-contrib-24.20190301~bpo9+1/check_ssl_cert/check_ssl_cert-1.83.0/check_ssl_cert')
-rwxr-xr-xnagios-plugins-contrib-24.20190301~bpo9+1/check_ssl_cert/check_ssl_cert-1.83.0/check_ssl_cert2483
1 files changed, 2483 insertions, 0 deletions
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_ssl_cert/check_ssl_cert-1.83.0/check_ssl_cert b/nagios-plugins-contrib-24.20190301~bpo9+1/check_ssl_cert/check_ssl_cert-1.83.0/check_ssl_cert
new file mode 100755
index 0000000..602b159
--- /dev/null
+++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_ssl_cert/check_ssl_cert-1.83.0/check_ssl_cert
@@ -0,0 +1,2483 @@
+#!/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 <matteo@corti.li>
+#
+# 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