From 0d2b955e21246808e1df821e1fd61b13a4378dee Mon Sep 17 00:00:00 2001 From: David Cooper Date: Wed, 22 May 2019 10:11:34 -0400 Subject: [PATCH] Check pwnedkeys.com database This PR adds a check of whether the server's public key appears in the https://pwnedkeys.com database. --- testssl.sh | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/testssl.sh b/testssl.sh index 4aa9f2a..7e79285 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1557,6 +1557,53 @@ http_get() { fi } +# Outputs the headers when downloading any URL (arg1) via HTTP 1.1 GET from port 80. +# Only works if curl or wget is available. +# There the environment variable is used automatically +# Currently it is being used by check_pwnedkeys() only. +http_get_header() { + local proto z + local node="" query="" + local dl="$2" + local useragent="$UA_STD" + local jsonID="http_get_header" + local headers + local -i ret + + "$SNEAKY" && useragent="$UA_SNEAKY" + + if type -p curl &>/dev/null; then + if [[ -z "$PROXY" ]]; then + headers="$(curl --head -s --noproxy '*' -A $''"$useragent"'' "$1")" + else + # for the sake of simplicity assume the proxy is using http + headers="$(curl --head -s -x $PROXYIP:$PROXYPORT -A $''"$useragent"'' "$1")" + fi + ret=$? + [[ $ret -eq 0 ]] && tm_out "$headers" + return $? + elif type -p wget &>/dev/null; then + # wget has no proxy command line. We need to use http_proxy instead. And for the sake of simplicity + # assume the GET protocol we query is using http -- http_proxy is the $ENV not for the connection TO + # the proxy, but for the protocol we query THROUGH the proxy + if [[ -z "$PROXY" ]]; then + headers="$(wget --no-proxy -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)" + else + if [[ -z "$http_proxy" ]]; then + headers="$(http_proxy=http://$PROXYIP:$PROXYPORT wget -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)" + else + headers="$(wget -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)" + fi + fi + ret=$? + [[ $ret -eq 0 ]] && tm_out "$headers" + [[ $ret -eq 8 ]] && tm_out "$headers" + return $? + else + return 1 + fi +} + ldap_get() { local ldif local -i success @@ -1578,6 +1625,54 @@ ldap_get() { fi } +# checks whether the public key in arg1 appears in the https://pwnedkeys.com/ database. +# arg1: file containing certificate +# arg2: public key algorithm +# arg3 key size +# Responses are as follows: +# 0 - not checked +# 1 - key not found in database +# 2 - key found in database +check_pwnedkeys() { + local cert="$1" + local cert_key_algo="$2" + local -i cert_keysize="$3" + local pubkey curve response + + "$PHONE_OUT" || return 0 + + # https://pwnedkeys.com only keeps records on 1024 bit and larger RSA keys, + # as well as elliptic-curve keys on the P-256, P-384, and P-521 curves. + if [[ "$cert_key_algo" =~ RSA ]] || [[ "$cert_key_algo" =~ rsa ]]; then + [[ $cert_keysize -ge 1024 ]] || return 0 + elif [[ "$cert_key_algo" =~ ecdsa ]] || [[ "$cert_key_algo" == *ecPublicKey ]]; then + [[ $cert_keysize -eq 256 ]] || [[ $cert_keysize -eq 384 ]] || \ + [[ $cert_keysize -eq 521 ]] || return 0 + else + return 0 + fi + + pubkey="$($OPENSSL x509 -in "$cert" -pubkey -noout 2>/dev/null)" + # If it is an elliptic curve key, check that it is P-256, P-384, or P-521. + if [[ "$cert_key_algo" =~ ecdsa ]] || [[ "$cert_key_algo" == *ecPublicKey ]]; then + curve="$($OPENSSL ec -pubin -text <<< "$pubkey" 2>/dev/null)" + curve="${curve#*ASN1 OID: }" + [[ "$curve" == prime256v1* ]] || [[ "$curve" == secp384r1* ]] || \ + [[ "$curve" == secp521r1* ]] || return 0 + fi + fingerprint="$($OPENSSL pkey -pubin -outform DER <<< "$pubkey" 2>/dev/null | $OPENSSL dgst -sha256 -hex 2>/dev/null)" + fingerprint="${fingerprint#*= }" + response="$(http_get_header "https://v1.pwnedkeys.com/$fingerprint")" + [[ $? -eq 0 ]] || return 0 + if [[ "$response" =~ "404 Not Found" ]]; then + return 1 + elif [[ "$response" =~ "200 OK" ]]; then + return 2 + else + return 0 + fi +} + check_revocation_crl() { local crl="$1" local jsonID="$2" @@ -8493,6 +8588,16 @@ certificate_info() { outln fi + if "$PHONE_OUT"; then + out "$indent"; pr_bold " https://pwnedkeys.com/ " + check_pwnedkeys "$HOSTCERT" "$cert_key_algo" "$cert_keysize" + case "$?" in + 0) outln "not checked"; fileout "pwnedkeys${json_postfix}" "INFO" "not checked" ;; + 1) outln "not in database"; fileout "pwnedkeys${json_postfix}" "INFO" "not in database" ;; + 2) prln_svrty_critical "key appears in database"; fileout "pwnedkeys${json_postfix}" "CRITICAL" "not checked" ;; + esac + fi + out "$indent"; pr_bold " Certificate Revocation List " jsonID="cert_crlDistributionPoints" # ~ get next 50 lines after pattern , strip until Signature Algorithm and retrieve URIs