├── .gitignore ├── LICENSE ├── README.md └── ssl-cert-check /.gitignore: -------------------------------------------------------------------------------- 1 | README.html 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2017, Rainer Müller 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssl-cert-check 2 | 3 | Usage: 4 | 5 | - `./ssl-cert-check ` 6 | - `./ssl-cert-check --list=` 7 | - `./ssl-cert-check --glob=` 8 | 9 | This tool will warn you if any of the specified certificates expires in the 10 | next \ days. If the --list mode is used, the file is expected to contain 11 | one certspec per line. Similarly, --glob mode expects one certspec per line in 12 | the given file, but each line will be evaluated as a wildcard glob pattern. In 13 | both cases, lines starting with the character '#' as well as empty lines will 14 | be ignored. 15 | 16 | ## Parameters 17 | 18 | The first parameter is the number of days to warn in advance for expiring 19 | certificates. All following parameters are treated as certificate 20 | specifications and can be in one of the following formats: 21 | 22 | - An absolute path to a x509 PEM certificate file
23 | For example: 24 | * /etc/apache2/ssl/example.org.pem 25 | 26 | - A file://\ URI
27 | For example: 28 | * file:///etc/apache2/ssl/example.org.pem 29 | 30 | - A ssl://\:\ URI
31 | For example: 32 | * ssl://example.org:443 33 | 34 | - A \://\[:\] URI, this is the same as ssl://\:\.
35 | The real port number is usually looked up in /etc/services, note that you often need the one with the 's' suffix, like "https", "imaps", etc.
36 | For example: 37 | * https://example.org 38 | * imaps://example.org (same as ssl://example.org:993) 39 | 40 | - A \+starttls://\[:\] URI
41 | Use the STARTTLS command to start a in-protocol TLS session after opening an unencrypted connection. The openssl s\_client needs to support this protocol. At time of this writing, the supported protocols are "smtp", "pop3", "imap", "ftp" and "xmpp".
42 | For example: 43 | * imap+starttls://example.org 44 | * smtp+starttls://example.org:587 45 | 46 | ## Examples 47 | 48 | Example for your crontab: 49 | 50 | MAILTO=root 51 | 6 6 * * * nobody /usr/local/bin/ssl-cert-check 30 /etc/apache2/ssl/*.crt /etc/ssl/certs/dovecot.pem https://localhost ssl://localhost:465 smtp+starttls://localhost:587 52 | -------------------------------------------------------------------------------- /ssl-cert-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set fenc=utf-8: 3 | 4 | # Copyright (c) 2013-2017, Rainer Müller 5 | # Copyright (c) 2015, Daniel Danner 6 | # Copyright (c) 2017, Mateusz Adamowski 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # * Redistributions of source code must retain the above copyright notice, this 13 | # list of conditions and the following disclaimer. 14 | # 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | # https://raim.codingfarm.de/blog/tag/ssl-cert-check/ 31 | # https://github.com/raimue/ssl-cert-check 32 | 33 | if [ $BASH_VERSINFO -lt 4 ]; then 34 | echo "Error: this script requires bash >= 4.0" >&2 35 | exit 3 36 | fi 37 | 38 | # First parameter specifies if certificate expire in the next X days 39 | DAYS=$1 40 | shift 41 | 42 | if [[ ! $DAYS =~ ^[0-9]+$ ]]; then 43 | echo "Error: missing parameter or invalid number" >&2 44 | exit 3 45 | fi 46 | 47 | # We need extended globbing 48 | shopt -s extglob 49 | 50 | if [[ "$1" =~ ^--(list|glob)=.* ]]; then 51 | LIST=${1##--@(list|glob)=} 52 | if [[ ! -r "$LIST" ]]; then 53 | echo "Error: list is not a regular file" >&2 54 | fi 55 | readarray -t TARGETS < <(grep -v '\(^#\|^\s*$\)' "$LIST") 56 | if [[ "$1" =~ ^--glob=.* ]]; then 57 | TARGETS=( ${TARGETS[@]} ) 58 | fi 59 | else 60 | TARGETS=("$@") 61 | fi 62 | 63 | exitcode=0 64 | 65 | for cert in "${TARGETS[@]}"; do 66 | enddate="" 67 | 68 | # For ease of use, map any absolute path name to a file:// URL 69 | if [[ $cert =~ ^/(.*)$ ]]; then 70 | cert=file://$cert 71 | fi 72 | 73 | # Split URI into protocol and target 74 | if [[ $cert =~ ^(.*)://(.*)$ ]]; then 75 | proto=${BASH_REMATCH[1]} 76 | target=${BASH_REMATCH[2]} 77 | else 78 | echo "Error: invalid certificate specification: $cert" >&2 79 | if [ $exitcode -lt 2 ]; then 80 | exitcode=2 81 | fi 82 | continue 83 | fi 84 | 85 | extra="" 86 | case $proto in 87 | file) 88 | enddate=$(openssl x509 -checkend $(( 86400 * DAYS )) -enddate -in "$target") 89 | ;; 90 | !(ssl)) 91 | # Handle special protocol definition for STARTTLS 92 | if [[ $proto =~ ^(.*)\+starttls$ ]]; then 93 | proto=${BASH_REMATCH[1]} 94 | extra="-starttls $proto" 95 | fi 96 | 97 | # If no port was given, use the default for this protocol 98 | if [[ ! $target =~ :[0-9]+$ ]]; then 99 | target+=:$proto 100 | fi 101 | 102 | # (intentional fallthrough) 103 | ;& 104 | ssl) 105 | # Extract servername for SNI 106 | servername=${target%:*} 107 | 108 | # Retrieve certificate 109 | # Create new temporary file 110 | tempfile=$(mktemp -t ssl-cert-check.XXXXX) 111 | if [ -z "$tempfile" ]; then 112 | echo "Error: unable to create temporary file" >&2 113 | if [ $exitcode -lt 2 ]; then 114 | exitcode=2 115 | fi 116 | continue 117 | fi 118 | # Delete temporary file if shell exits during certificate check 119 | trap "rm -f \"$tempfile\"" EXIT 120 | cmd=(openssl s_client -connect "$target" -servername "$servername" $extra) 121 | certificate=$(echo | "${cmd[@]}" 2>"$tempfile" \ 122 | | sed -n -e '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \ 123 | -e '/-----END CERTIFICATE-----/q') 124 | if [ "$certificate" == "" ]; then 125 | echo "Error: unable to check $cert" >&2 126 | echo "Error: command was '${cmd[*]}', openssl error messages were:" >&2 127 | sed 's/^/ /' "$tempfile" >&2 128 | if [ $exitcode -lt 2 ]; then 129 | exitcode=2 130 | fi 131 | continue 132 | else 133 | # Extract notAfter date of validity 134 | enddate=$(echo "$certificate" | openssl x509 -checkend $(( 86400 * DAYS )) -enddate) 135 | fi 136 | rm -f "$tempfile" 137 | ;; 138 | esac 139 | 140 | if [[ $enddate =~ (.*)Certificate\ will\ expire ]]; then 141 | echo "==> Certificate $cert is about to expire soon:" 142 | echo -n " ${BASH_REMATCH[1]}" 143 | if [ $exitcode -lt 1 ]; then 144 | exitcode=1 145 | fi 146 | fi 147 | done 148 | 149 | exit $exitcode 150 | --------------------------------------------------------------------------------