921 lines
28 KiB
Bash
Executable file
921 lines
28 KiB
Bash
Executable file
#!/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.
|
|
#
|
|
# 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.
|
|
#
|
|
# RCS information
|
|
# enable substitution with:
|
|
# $ svn propset svn:keywords "Id Revision HeadURL Source Date"
|
|
#
|
|
# $Id: check_ssl_cert 1384 2014-11-29 15:08:58Z corti $
|
|
# $Revision: 1384 $
|
|
# $HeadURL: https://svn.id.ethz.ch/nagios_plugins/check_ssl_cert/check_ssl_cert $
|
|
# $Date: 2014-11-29 16:08:58 +0100 (Sat, 29 Nov 2014) $
|
|
|
|
################################################################################
|
|
# Constants
|
|
|
|
VERSION=1.17.0
|
|
SHORTNAME="SSL_CERT"
|
|
|
|
VALID_ATTRIBUTES=",startdate,enddate,subject,issuer,modulus,serial,hash,email,ocsp_uri,fingerprint,"
|
|
|
|
################################################################################
|
|
# 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 " -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 " -h,--help,-? this help message"
|
|
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, serial,"
|
|
echo " hash, email, ocsp_uri and fingerprint."
|
|
echo " 'all' will include all the available attributes."
|
|
echo " -i,--issuer issuer pattern to match the issuer of the certificate"
|
|
echo " -n,--cn name pattern to match the CN of the certificate"
|
|
echo " -N,--host-cn match CN with the host name"
|
|
echo " --ocsp check revocation via OCSP"
|
|
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 {http|smtp|pop3|imap|ftp|xmpp}"
|
|
echo " http: default"
|
|
echo " smtp,pop3,imap,ftp: switch to TLS"
|
|
echo " -s,--selfsigned allows self-signed certificates"
|
|
echo " -S,--ssl version force SSL version (2,3)"
|
|
echo " -r,--rootcert path root certificate or directory to be used for"
|
|
echo " certficate validation"
|
|
echo " -t,--timeout seconds timeout after the specified time"
|
|
echo " (defaults to 15 seconds)"
|
|
echo " --temp dir directory where to store the temporary files"
|
|
echo " --tls1 force TLS version 1"
|
|
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
|
|
echo "Deprecated options:"
|
|
echo " -d,--days days minimum number of days a certificate has to be valid"
|
|
echo " (see --critical and --warning)"
|
|
echo
|
|
echo "Report bugs to: Matteo Corti <matteo@corti.li>"
|
|
echo
|
|
|
|
exit 3
|
|
|
|
}
|
|
|
|
################################################################################
|
|
# Exits with a critical message
|
|
# Params
|
|
# $1 error message
|
|
critical() {
|
|
if [ -n "${CN}" ] ; then
|
|
tmp=" ${CN}"
|
|
fi
|
|
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 "${CN}" ] ; then
|
|
tmp=" ${CN}"
|
|
fi
|
|
printf '%s WARN%s: %s%s%s\n' "${SHORTNAME}" "${tmp}" "$1" "${PERFORMANCE_DATA}" "${LONG_OUTPUT}"
|
|
exit 1
|
|
}
|
|
|
|
################################################################################
|
|
# Exits with an 'unkown' status
|
|
# Param
|
|
# $1 message
|
|
unknown() {
|
|
if [ -n "${CN}" ] ; then
|
|
tmp=" ${CN}"
|
|
fi
|
|
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 "${TIMEOUT_BIN}" ] ; then
|
|
|
|
eval "${TIMEOUT_BIN} $time $command"
|
|
|
|
elif [ -n "${EXPECT}" ] ; then
|
|
|
|
expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }"
|
|
|
|
if [ $? = 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=$(which "$1" 2> /dev/null)
|
|
|
|
if [ -z "$PROG" ] ; then
|
|
critical "cannot find $1"
|
|
fi
|
|
|
|
if [ ! -x "$PROG" ] ; then
|
|
critical "$PROG is not executable"
|
|
fi
|
|
|
|
}
|
|
|
|
################################################################################
|
|
# Tries to fetch the certificate
|
|
|
|
fetch_certificate() {
|
|
|
|
# check if a protocol was specified (if not HTTP switch to TLS)
|
|
if [ -n "${PROTOCOL}" ] && [ "${PROTOCOL}" != "http" ] && [ "${PROTOCOL}" != "https" ] ; then
|
|
|
|
case "${PROTOCOL}" in
|
|
|
|
smtp|pop3|imap|ftp|xmpp)
|
|
|
|
exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -starttls ${PROTOCOL} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} 2> ${ERROR} 1> ${CERT}"
|
|
;;
|
|
|
|
*)
|
|
|
|
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}"
|
|
|
|
else
|
|
|
|
unknown "Error: option 'file' works with -H localhost only"
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
exec_with_timeout "$TIMEOUT" "echo 'Q' | $OPENSSL s_client ${CLIENT} ${CLIENTPASS} -connect $HOST:$PORT ${SERVERNAME} -verify 6 ${ROOT_CA} ${SSL_VERSION} 2> ${ERROR} 1> ${CERT}"
|
|
|
|
fi
|
|
|
|
if [ $? -ne 0 ] ; then
|
|
critical "Error: $(head -n 1 ${ERROR})"
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
main() {
|
|
|
|
################################################################################
|
|
# Main
|
|
################################################################################
|
|
|
|
# default values
|
|
PORT=443
|
|
TIMEOUT=15
|
|
VERBOSE=""
|
|
OPENSSL=""
|
|
|
|
# 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 ;;
|
|
|
|
-h|--help|-\?) usage; exit 0 ;;
|
|
|
|
-N|--host-cn) COMMON_NAME="__HOST__"; shift ;;
|
|
|
|
-s|--selfsigned) SELFSIGNED=1; shift ;;
|
|
|
|
--tls1) SSL_VERSION="-tls1"; shift ;;
|
|
|
|
--ocsp) OCSP=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 ;;
|
|
|
|
# deprecated option: used to be as --warning
|
|
-d|--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 ;;
|
|
|
|
-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"
|
|
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
|
|
COMMON_NAME=$2; 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; 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 ;;
|
|
|
|
-C|--clientcert) if [ $# -gt 1 ]; then
|
|
CLIENT_CERT=$2; shift 2
|
|
else
|
|
unknown "-c,--clientcert requires an argument"
|
|
fi ;;
|
|
|
|
--clientpass) if [ $# -gt 1 ]; then
|
|
CLIENT_PASS=$2; shift 2
|
|
else
|
|
unknown "--clientpass requires an argument"
|
|
fi ;;
|
|
|
|
-S|--ssl) if [ $# -gt 1 ]; then
|
|
if [ "$2" = "2" -o "$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 ;;
|
|
|
|
########################################
|
|
# special
|
|
|
|
--) shift; break;;
|
|
-*) unknown "invalid option: $1" ;;
|
|
*) 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 "${CLIENT_CERT}" ] ; then
|
|
if [ ! -r "${CLIENT_CERT}" ] ; then
|
|
unknown "Cannot read client certificate ${CLIENT_CERT}"
|
|
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##*/}" != 'openssl' ] ; then
|
|
unknown "${OPENSSL} ist not an openssl executable"
|
|
fi
|
|
fi
|
|
|
|
#######################
|
|
# Check needed programs
|
|
|
|
# OpenSSL
|
|
if [ -z "${OPENSSL}" ] ; then
|
|
check_required_prog openssl
|
|
OPENSSL=$PROG
|
|
fi
|
|
|
|
# Expect (optional)
|
|
EXPECT=$(which 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=$(which 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 with Date::Parse (optional)
|
|
PERL=$(which perl 2> /dev/null)
|
|
test -x "${PERL}" || PERL=""
|
|
if [ -z "${PERL}" ] && [ -n "${VERBOSE}" ] ; then
|
|
echo "Perl not found: disabling date computations"
|
|
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=""
|
|
fi
|
|
|
|
################################################################################
|
|
# check if openssl s_client supports the -servername option
|
|
#
|
|
# openssl s_client does not have a -help option
|
|
# => we supply an invalid command line option to get the help
|
|
# on standard error
|
|
#
|
|
SERVERNAME=
|
|
if ${OPENSSL} s_client not_a_real_option 2>&1 | grep -q -- -servername ; then
|
|
|
|
if [ -n "${COMMON_NAME}" ] ; then
|
|
SERVERNAME="-servername ${COMMON_NAME}"
|
|
fi
|
|
|
|
else
|
|
if [ -n "${VERBOSE}" ] ; then
|
|
echo "'${OPENSSL} s_client' does not support '-servername': disabling virtual server support"
|
|
fi
|
|
fi
|
|
|
|
################################################################################
|
|
# fetch the X.509 certificate
|
|
|
|
# temporary storage for the certificate and the errors
|
|
|
|
CERT=$( mktemp -t "${0##*/}XXXXXX" 2> /dev/null )
|
|
if [ -z "${CERT}" ] || [ ! -w "${CERT}" ] ; then
|
|
unknown 'temporary file creation failure.'
|
|
fi
|
|
|
|
ERROR=$( mktemp -t "${0##*/}XXXXXX" 2> /dev/null )
|
|
if [ -z "${ERROR}" ] || [ ! -w "${ERROR}" ] ; then
|
|
unknown 'temporary file creation failure.'
|
|
fi
|
|
|
|
if [ -n "${OCSP}" ] ; then
|
|
ISSUER_CERT=$( mktemp -t "${0##*/}XXXXXX" 2> /dev/null )
|
|
if [ -z "${ISSUER_CERT}" ] || [ ! -w "${ISSUER_CERT}" ] ; then
|
|
unknown 'temporary file creation failure.'
|
|
fi
|
|
fi
|
|
|
|
if [ -n "${VERBOSE}" ] ; then
|
|
echo "downloading certificate to ${TMPDIR}"
|
|
fi
|
|
|
|
CLIENT=""
|
|
if [ -n "${CLIENT_CERT}" ] ; then
|
|
CLIENT="-cert ${CLIENT_CERT}"
|
|
fi
|
|
|
|
CLIENTPASS=""
|
|
if [ -n "${CLIENT_PASS}" ] ; then
|
|
CLIENTPASS="-pass pass:${CLIENT_PASS}"
|
|
fi
|
|
|
|
# cleanup before program termination
|
|
# using named signals to be POSIX compliant
|
|
trap 'rm -f $CERT $ERROR $ISSUER_CERT' EXIT HUP INT QUIT TERM
|
|
|
|
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 "CERTIFICATE" "${CERT}" ; then
|
|
if [ -n "${FILE}" ] ; then
|
|
critical "'${FILE}' is not a valid certificate file"
|
|
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 (${ERROR_MESSAGE})"
|
|
fi
|
|
fi
|
|
|
|
################################################################################
|
|
# parse the X.509 certificate
|
|
|
|
DATE=$($OPENSSL x509 -in "${CERT}" -enddate -noout | sed -e "s/^notAfter=//")
|
|
CN=$($OPENSSL x509 -in "${CERT}" -subject -noout | sed -e "s/^.*\/CN=//" -e "s/\/[A-Za-z][A-Za-z]*=.*\$//")
|
|
|
|
CA_O=$($OPENSSL x509 -in "${CERT}" -issuer -noout | sed -e "s/^.*\/O=//" -e "s/\/[A-Z][A-Z]*=.*\$//")
|
|
CA_CN=$($OPENSSL x509 -in "${CERT}" -issuer -noout | sed -e "s/^.*\/CN=//" -e "s/\/[A-Za-z][A-Za-z]*=.*\$//")
|
|
|
|
OCSP_URI=$($OPENSSL x509 -in "${CERT}" -ocsp_uri -noout)
|
|
|
|
ISSUER_URI=$($OPENSSL x509 -in "${CERT}" -text -noout | grep "CA Issuers" | sed -e "s/^.*CA Issuers - URI://")
|
|
|
|
################################################################################
|
|
# 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
|
|
value=$(${OPENSSL} x509 -in "${CERT}" -noout -"${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
|
|
|
|
fi
|
|
|
|
################################################################################
|
|
# compute for how many days the certificate will be valid
|
|
|
|
if [ -n "${PERL}" ] ; then
|
|
|
|
CERT_END_DATE=$($OPENSSL x509 -in "${CERT}" -noout -enddate | sed -e "s/.*=//")
|
|
|
|
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
|
|
|
|
)
|
|
|
|
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
|
|
|
|
PERFORMANCE_DATA="|days=$DAYS_VALID;${WARNING};${CRITICAL};;"
|
|
|
|
fi
|
|
|
|
|
|
|
|
################################################################################
|
|
# check the CN (this will not work as expected with wildcard certificates)
|
|
|
|
if [ -n "$COMMON_NAME" ] ; then
|
|
|
|
ok=''
|
|
|
|
case $COMMON_NAME in
|
|
$CN) ok='true' ;;
|
|
esac
|
|
|
|
# check alterante names
|
|
if [ -n "${ALTNAMES}" ] ; then
|
|
for alt_name in $( $OPENSSL x509 -in "${CERT}" -text | \
|
|
grep --after-context=1 '509v3 Subject Alternative Name:' | \
|
|
tail -n 1 | sed -e "s/DNS://g" | sed -e "s/,//g" ) ; do
|
|
case $COMMON_NAME in
|
|
$alt_name) ok='true' ;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
if [ -z "$ok" ] ; then
|
|
critical "invalid CN ('$CN' does not match '$COMMON_NAME')"
|
|
fi
|
|
|
|
fi
|
|
|
|
################################################################################
|
|
# check the issuer
|
|
|
|
if [ -n "$ISSUER" ] ; then
|
|
|
|
ok=''
|
|
CA_ISSUER_MATCHED=''
|
|
|
|
if echo "$CA_CN" | grep -q "^${ISSUER}\$" ; then
|
|
ok='true'
|
|
CA_ISSUER_MATCHED="${CA_CN}"
|
|
fi
|
|
|
|
if echo "$CA_O" | grep -q "^${ISSUER}\$" ; then
|
|
ok='true'
|
|
CA_ISSUER_MATCHED="${CA_O}"
|
|
fi
|
|
|
|
if [ -z "$ok" ] ; then
|
|
critical "invalid CA ('$ISSUER' does not match '$CA_O' or '$CA_CN')"
|
|
fi
|
|
|
|
else
|
|
|
|
CA_ISSUER_MATCHED="${CA_CN}"
|
|
|
|
fi
|
|
|
|
################################################################################
|
|
# check the validity
|
|
|
|
# we always check expired certificates
|
|
if ! $OPENSSL x509 -in "${CERT}" -noout -checkend 0 ; then
|
|
critical "certificate is expired (was valid until $DATE)"
|
|
fi
|
|
|
|
if [ -n "${CRITICAL}" ] ; then
|
|
|
|
if ! $OPENSSL x509 -in "${CERT}" -noout -checkend $(( CRITICAL * 86400 )) ; then
|
|
critical "certificate will expire on $DATE"
|
|
fi
|
|
|
|
fi
|
|
|
|
if [ -n "${WARNING}" ] ; then
|
|
|
|
if ! $OPENSSL x509 -in "${CERT}" -noout -checkend $(( WARNING * 86400 )) ; then
|
|
warning "certificate will expire on $DATE"
|
|
fi
|
|
|
|
fi
|
|
|
|
################################################################################
|
|
# check revocation via OCSP
|
|
|
|
if [ -n "${OCSP}" ]; then
|
|
|
|
curl --silent "${ISSUER_URI}" > "${ISSUER_CERT}"
|
|
|
|
if file "${ISSUER_CERT}" | grep -q ': data' ; then
|
|
openssl x509 -inform DER -outform PEM -in "${ISSUER_CERT}" -out "${ISSUER_CERT}"
|
|
fi
|
|
|
|
if "$OPENSSL" ocsp -no_nonce -issuer "${ISSUER_CERT}" -cert "${CERT}" -url "${OCSP_URI}" 2>&1 | grep -qi "revoked" ; then
|
|
critical "certificate is revoked"
|
|
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 certficate 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\nself signed certificate"
|
|
else
|
|
SELFSIGNEDCERT="self signed "
|
|
fi
|
|
|
|
else
|
|
|
|
# process errors
|
|
details=$(grep '^verify\ error:' "${ERROR}" | sed -e "s/verify\ error:num=[0-9]*:/verification error: /" )
|
|
|
|
critical "Cannot verify certificate\n${details}"
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
################################################################################
|
|
# If we get this far, assume all is well. :)
|
|
|
|
# if --altnames was specified we show the specified CN instead of
|
|
# the certificate CN
|
|
if [ -n "${ALTNAMES}" ] && [ -n "${COMMON_NAME}" ] ; then
|
|
CN=${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
|
|
|
|
echo "${SHORTNAME} OK - X.509 ${SELFSIGNEDCERT}certificate for '${CN}' from '${CA_ISSUER_MATCHED}' valid until ${DATE}${DAYS_VALID}${PERFORMANCE_DATA}${LONG_OUTPUT}"
|
|
|
|
exit 0
|
|
|
|
}
|
|
|
|
if [ "${1}" != "--source-only" ]; then
|
|
main "${@}"
|
|
fi
|