|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Define colors uses for status output |
| 4 | +RED=$(tput setaf 1 2> /dev/null) |
| 5 | +GREEN=$(tput setaf 2 2> /dev/null) |
| 6 | +RESET=$(tput sgr0 2> /dev/null) |
| 7 | + |
| 8 | +CABUNDLE_MAX_ISSUERS=32 |
| 9 | + |
| 10 | +function usage () { |
| 11 | + cat <<HELP >&2 |
| 12 | +Verifies, that custom SSL certificate files are usable as part of the installation. |
| 13 | +When passing filenames use absolute paths. |
| 14 | +
|
| 15 | +usage: $0 -c CERT_FILE -k KEY_FILE -b CA_BUNDLE_FILE |
| 16 | +HELP |
| 17 | +} |
| 18 | + |
| 19 | +while getopts "c:k:b:" opt; do |
| 20 | + case $opt in |
| 21 | + c) |
| 22 | + CERT_FILE="$(readlink -f $OPTARG)" |
| 23 | + ;; |
| 24 | + k) |
| 25 | + KEY_FILE="$(readlink -f $OPTARG)" |
| 26 | + ;; |
| 27 | + b) |
| 28 | + CA_BUNDLE_FILE="$(readlink -f $OPTARG)" |
| 29 | + ;; |
| 30 | + h) |
| 31 | + usage |
| 32 | + exit 0 |
| 33 | + ;; |
| 34 | + ?) |
| 35 | + usage |
| 36 | + exit 1 |
| 37 | + ;; |
| 38 | + esac |
| 39 | +done |
| 40 | + |
| 41 | +EXIT_CODE=0 |
| 42 | + |
| 43 | +if [ -z "$CERT_FILE" -o -z "$KEY_FILE" -o -z "$CA_BUNDLE_FILE" ]; then |
| 44 | + echo 'One of the required parameters is missing.' >&2 |
| 45 | + usage |
| 46 | + exit 1 |
| 47 | +fi |
| 48 | + |
| 49 | +function error () { |
| 50 | + echo -e "\n${RED}[FAIL]${RESET}\n" |
| 51 | + CURRENT_EXIT_CODE=$1 |
| 52 | + EXIT_CODE=$((EXIT_CODE|CURRENT_EXIT_CODE)) |
| 53 | + echo -e $2 >&2 |
| 54 | +} |
| 55 | + |
| 56 | +function success () { |
| 57 | + echo -e "\n${GREEN}[OK]${RESET}\n" |
| 58 | +} |
| 59 | + |
| 60 | +function check-files-exist() { |
| 61 | + if [[ ! -f "$CA_BUNDLE_FILE" ]] || [[ ! -f "$CERT_FILE" ]] || [[ ! -f "$KEY_FILE" ]] ; then |
| 62 | + echo "One of '${CERT_FILE}', '${KEY_FILE}' or '${CA_BUNDLE_FILE}' not found" >&2 |
| 63 | + exit 1 |
| 64 | + fi |
| 65 | +} |
| 66 | + |
| 67 | +function check-server-cert-encoding () { |
| 68 | + printf 'Checking server certificate encoding: ' |
| 69 | + openssl x509 -inform pem -in $CERT_FILE -noout -text &> /dev/null |
| 70 | + if [[ $? == "0" ]]; then |
| 71 | + success |
| 72 | + else |
| 73 | + openssl x509 -inform der -in $CERT_FILE -noout -text &> /dev/null |
| 74 | + if [[ $? == "0" ]]; then |
| 75 | + error 8 "The server certificate is in DER encoding, which is incompatible.\n\n" |
| 76 | + printf "Run the following command to convert the certificate to PEM encoding,\n" |
| 77 | + printf "then test it again.\n" |
| 78 | + printf "# openssl x509 -in %s -outform pem -out %s.pem\n\n" $(basename $CERT_FILE) $(basename $CERT_FILE) |
| 79 | + printf "When you run $(basename $0) again, use file\n" |
| 80 | + printf "%s.pem for the server certificate.\n\n" $(basename $CERT_FILE) |
| 81 | + else |
| 82 | + error 9 "The encoding of the server certificate is unknown." |
| 83 | + fi |
| 84 | + fi |
| 85 | +} |
| 86 | + |
| 87 | +function check-expiration () { |
| 88 | + CERT_EXP=$(openssl x509 -noout -enddate -in $CERT_FILE | sed -e 's/notAfter=//' | awk '{$NF="";}1') |
| 89 | + CA_EXP=$(openssl x509 -noout -enddate -in $CA_BUNDLE_FILE | sed -e 's/notAfter=//' | awk '{$NF="";}1') |
| 90 | + DATE_TODAY=$(date -u +%Y%m%d%H%M%S) |
| 91 | + CERT_DATE=$(date -d"${CERT_EXP}" +%Y%m%d%H%M%S) |
| 92 | + CA_DATE=$(date -d"${CA_EXP}" +%Y%m%d%H%M%S) |
| 93 | + printf "Checking expiration of certificate: " |
| 94 | + if [[ $DATE_TODAY -gt $CERT_DATE ]]; then |
| 95 | + error 6 "The certificate \"$CERT_FILE\" has already expired on: $CERT_EXP" |
| 96 | + else |
| 97 | + success |
| 98 | + fi |
| 99 | + printf "Checking expiration of CA bundle: " |
| 100 | + if [[ $DATE_TODAY -gt $CA_DATE ]]; then |
| 101 | + error 7 "The CA bundle \"$CA_BUNDLE_FILE\" has already expired on: $CA_EXP" |
| 102 | + else |
| 103 | + success |
| 104 | + fi |
| 105 | +} |
| 106 | + |
| 107 | +function check-cert-ca-flag () { |
| 108 | + printf "Checking if server certificate has CA:TRUE flag " |
| 109 | + openssl x509 -in $CERT_FILE -noout -text | grep -q CA:TRUE |
| 110 | + if [[ $? -ne 0 ]]; then |
| 111 | + success |
| 112 | + else |
| 113 | + error 7 "The server certificate is marked as a CA and can not be used." |
| 114 | + fi |
| 115 | +} |
| 116 | + |
| 117 | +function check-passphrase () { |
| 118 | + printf "Checking for private key passphrase: " |
| 119 | + CHECK=$(cat $KEY_FILE | grep ENCRYPTED) |
| 120 | + if [[ $? == "0" ]]; then |
| 121 | + error 2 "The $KEY_FILE contains a passphrase, remove the key's passphrase by doing: |
| 122 | + \nmv $KEY_FILE $KEY_FILE.old |
| 123 | + \nopenssl pkey -in $KEY_FILE.old -out $KEY_FILE" |
| 124 | + exit 2 |
| 125 | + else |
| 126 | + success |
| 127 | + fi |
| 128 | +} |
| 129 | + |
| 130 | +function check-priv-key () { |
| 131 | + printf "Checking to see if the private key matches the certificate: " |
| 132 | + CERT_PUBKEY_SUM=$(openssl x509 -noout -pubkey -in $CERT_FILE | openssl sha512) |
| 133 | + KEY_PUBKEY_SUM=$(openssl pkey -pubout -in $KEY_FILE | openssl sha512) |
| 134 | + if [[ "$CERT_PUBKEY_SUM" != "$KEY_PUBKEY_SUM" ]]; then |
| 135 | + error 2 "The $KEY_FILE does not match the $CERT_FILE" |
| 136 | + else |
| 137 | + success |
| 138 | + fi |
| 139 | +} |
| 140 | + |
| 141 | +function check-ca-bundle () { |
| 142 | + printf "Checking CA bundle against the certificate file: " |
| 143 | + ERROR_PATTERN="error [0-9]+ at" |
| 144 | + CHECK=$(openssl verify -no-CApath -no-CAstore -CAfile $CA_BUNDLE_FILE -purpose sslserver -verbose $CERT_FILE 2>&1) |
| 145 | + CHECK_STATUS=$? |
| 146 | + |
| 147 | + if [[ $CHECK_STATUS != "0" || $CHECK =~ $ERROR_PATTERN ]]; then |
| 148 | + error 4 "The $CA_BUNDLE_FILE does not verify the $CERT_FILE" |
| 149 | + echo -e "${CHECK/OK/}\n" |
| 150 | + else |
| 151 | + success |
| 152 | + fi |
| 153 | +} |
| 154 | + |
| 155 | +function check-ca-bundle-size () { |
| 156 | + printf "Checking CA bundle size: " |
| 157 | + CHECK=$(grep -c "^--*BEGIN" $CA_BUNDLE_FILE) |
| 158 | + printf $CHECK |
| 159 | + if [[ $CHECK -lt $CABUNDLE_MAX_ISSUERS ]]; then |
| 160 | + success |
| 161 | + else |
| 162 | + CERRTISSUER=$(openssl x509 -noout -in $CERT_FILE -issuer 2>&1) |
| 163 | + error 10 "The CA bundle counts $CHECK issuers. Please trim your CA bundle and include only the certs relevant to your cert file" |
| 164 | + echo $CERTISSUER |
| 165 | + echo |
| 166 | + fi |
| 167 | +} |
| 168 | + |
| 169 | +function check-ca-bundle-trust-rules () { |
| 170 | + printf "Checking if CA bundle has trust rules: " |
| 171 | + CHECK=$(grep 'BEGIN TRUSTED CERTIFICATE' $CA_BUNDLE_FILE| wc -l) |
| 172 | + printf $CHECK |
| 173 | + if [[ $CHECK -lt 1 ]]; then |
| 174 | + success |
| 175 | + else |
| 176 | + error 10 "The CA bundle contains $CHECK certificate(s) with trust rules. This will create problems for older systems. Please, recreate the bundle using certificates without trust rules." |
| 177 | + echo |
| 178 | + fi |
| 179 | +} |
| 180 | + |
| 181 | +function check-cert-san () { |
| 182 | + printf "Checking Subject Alt Name on certificate " |
| 183 | + DNS_LIST=$(openssl x509 -noout -text -in $CERT_FILE | grep DNS:) |
| 184 | + if [[ $? == "0" ]]; then |
| 185 | + success |
| 186 | + printf "Checking if any Subject Alt Name on certificate matches the Subject CN" |
| 187 | + SUBJECT_CN=$(openssl x509 -in $CERT_FILE -noout -subject -nameopt multiline|grep commonName|cut -d '=' -f 2|tr -d ' ') |
| 188 | + for DNS in ${DNS_LIST} |
| 189 | + do |
| 190 | + DNS_VALUE="$( echo ${DNS//DNS:/} | tr -d ',')" |
| 191 | + if [ $SUBJECT_CN == $DNS_VALUE ] |
| 192 | + then |
| 193 | + success |
| 194 | + return |
| 195 | + fi |
| 196 | + done |
| 197 | + error 11 "The $CERT_FILE does not have a Subject Alt Name matching the Subject CN" |
| 198 | + else |
| 199 | + error 11 "The $CERT_FILE does not contain a Subject Alt Name. Common Name is deprecated, use Subject Alt Name instead. See: https://tools.ietf.org/html/rfc2818#section-3.1" |
| 200 | + fi |
| 201 | +} |
| 202 | + |
| 203 | +function check-cert-usage-key-encipherment () { |
| 204 | + printf "Checking Key Usage extension on certificate for Key Encipherment " |
| 205 | + CHECK=$(openssl x509 -noout -text -in $CERT_FILE | grep -A1 'X509v3 Key Usage:' | grep 'Key Encipherment') |
| 206 | + if [[ $? == "0" ]]; then |
| 207 | + success |
| 208 | + else |
| 209 | + error 4 "The $CERT_FILE does not allow for the 'Key Encipherment' key usage." |
| 210 | + fi |
| 211 | +} |
| 212 | + |
| 213 | +function check-shortname () { |
| 214 | + printf "Checking for use of shortname as CN" |
| 215 | + |
| 216 | + SUBJECT_CN=$(openssl x509 -in $CERT_FILE -noout -subject -nameopt multiline|grep commonName|cut -d '=' -f 2|tr -d ' ') |
| 217 | + if [[ $SUBJECT_CN != *"."* ]]; then |
| 218 | + error 1 "The $(basename $CERT_FILE) is using a shortname for Common Name (CN) and cannot be used.\n" |
| 219 | + fi |
| 220 | + |
| 221 | + DNS_LIST=$(openssl x509 -noout -text -in $CERT_FILE | grep DNS:) |
| 222 | + if [[ $? == "0" ]]; then |
| 223 | + for DNS in ${DNS_LIST} |
| 224 | + do |
| 225 | + DNS_VALUE="$( echo ${DNS//DNS:/} | tr -d ',')" |
| 226 | + |
| 227 | + if [[ $DNS_VALUE == *"."* ]]; then |
| 228 | + success |
| 229 | + return |
| 230 | + fi |
| 231 | + done |
| 232 | + error 1 "The $(basename $CERT_FILE) is using only shortnames for Subject Alt Name and cannot be used.\n" |
| 233 | + fi |
| 234 | +} |
| 235 | + |
| 236 | +function check-ca-signing-algorithm () { |
| 237 | + printf "Checking CA signing algorithm for sha1: " |
| 238 | + CHECK=$(openssl crl2pkcs7 -nocrl -certfile "$CA_BUNDLE_FILE" | openssl pkcs7 -print | grep algorithm | grep -q 'sha1WithRSAEncryption') |
| 239 | + if [[ $? == "0" ]]; then |
| 240 | + error 4 "The file '$CA_BUNDLE_FILE' contains a certificate signed with sha1 and will break installation. Update the server CA certificate and its chain with one signed by sha256 or stronger." |
| 241 | + else |
| 242 | + success |
| 243 | + fi |
| 244 | +} |
| 245 | + |
| 246 | +check-files-exist |
| 247 | +check-server-cert-encoding |
| 248 | +check-expiration |
| 249 | +check-cert-ca-flag |
| 250 | +check-passphrase |
| 251 | +check-priv-key |
| 252 | +check-ca-bundle |
| 253 | +check-ca-bundle-size |
| 254 | +check-ca-bundle-trust-rules |
| 255 | +check-cert-san |
| 256 | +check-cert-usage-key-encipherment |
| 257 | +check-shortname |
| 258 | +check-ca-signing-algorithm |
| 259 | + |
| 260 | +if [[ $EXIT_CODE == "0" ]]; then |
| 261 | + echo -e "${GREEN}Validation succeeded${RESET}\n" |
| 262 | +else |
| 263 | + exit $EXIT_CODE |
| 264 | +fi |
0 commit comments