From 79c9536d478fa2f7d9ea12694f7da093968cc4b2 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 15 May 2018 16:50:57 -0400 Subject: [PATCH 1/5] Add OCSP checking This commit adds the option to query the OCSP server(s) specified in a server's certificate for the certificate's revocation status. --- testssl.sh | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9c8b3d8..2d0dad9 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1493,6 +1493,36 @@ check_revocation_crl() { return 0 } +check_revocation_ocsp() { + local uri="$1" + local jsonID="$2" + local tmpfile="" + local -i success + + "$PHONE_OUT" || return 0 + tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE + $OPENSSL ocsp -no_nonce -url "$uri" -issuer $TEMPDIR/hostcert_issuer.pem \ + -verify_other $TEMPDIR/intermediatecerts.pem \ + -CAfile $TEMPDIR/intermediatecerts.pem -cert $HOSTCERT &> "$tmpfile" + if [[ $? -eq 0 ]] && grep -q "Response verify OK" "$tmpfile"; then + if grep -q "$HOSTCERT: good" "$tmpfile"; then + out ", " + pr_svrty_good "not revoked" + fileout "$jsonID" "OK" "not revoked" + elif grep -q "$HOSTCERT: revoked" "$tmpfile"; then + out ", " + pr_svrty_critical "revoked" + fileout "$jsonID" "CRITICAL" "revoked" + elif [[ $DEBUG -ge 2 ]]; then + outln + cat "$tmpfile" + fi + elif [[ $DEBUG -ge 2 ]]; then + outln + cat "$tmpfile" + fi +} + wait_kill(){ local pid=$1 # pid we wait for or kill local maxsleep=$2 # how long we wait before killing @@ -6452,6 +6482,7 @@ extract_certificates() { echo "" > $TEMPDIR/intermediatecerts.pem else cat level?.crt > $TEMPDIR/intermediatecerts.pem + cp level1.crt $TEMPDIR/hostcert_issuer.pem rm level?.crt fi fi @@ -7510,9 +7541,27 @@ certificate_info() { fileout "${jsonID}${json_postfix}" "INFO" "--" else if [[ $(count_lines "$ocsp_uri") -eq 1 ]]; then - outln "$ocsp_uri" + out "$ocsp_uri" + if [[ "$expfinding" != "expired" ]]; then + check_revocation_ocsp "$ocsp_uri" "cert_ocspRevoked${json_postfix}" + fi + ret=$((ret +$?)) + outln else - out_row_aligned "$ocsp_uri" "$spaces" + first_ocsp=true + while read -r line; do + if "$first_ocsp"; then + first_ocsp=false + else + out "$spaces" + fi + out "$line" + if [[ "$expfinding" != "expired" ]]; then + check_revocation_ocsp "$line" "cert_ocspRevoked${json_postfix}" + ret=$((ret +$?)) + fi + outln + done <<< "$ocsp_uri" fi fileout "${jsonID}${json_postfix}" "INFO" "$ocsp_uri" fi @@ -7609,7 +7658,7 @@ run_server_defaults() { local -i i n local -i certs_found=0 local -i ret=0 - local -a previous_hostcert previous_hostcert_txt previous_hostcert_type previous_intermediates keysize cipher + local -a previous_hostcert previous_hostcert_txt previous_hostcert_type previous_hostcert_issuer previous_intermediates keysize cipher local -a ocsp_response ocsp_response_status sni_used tls_version ct local -a ciphers_to_test certificate_type local -a -i success @@ -7740,6 +7789,8 @@ run_server_defaults() { previous_hostcert[certs_found]=$newhostcert previous_hostcert_txt[certs_found]="$($OPENSSL x509 -noout -text 2>>$ERRFILE <<< "$newhostcert")" previous_intermediates[certs_found]=$(cat $TEMPDIR/intermediatecerts.pem) + previous_hostcert_issuer[certs_found]="" + [[ -n "${previous_intermediates[certs_found]}" ]] && previous_hostcert_issuer[certs_found]=$(cat $TEMPDIR/hostcert_issuer.pem) [[ $n -ge 10 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI" tls_version[certs_found]="$DETECTED_TLS_VERSION" previous_hostcert_type[certs_found]=" ${certificate_type[n]}" @@ -7909,6 +7960,7 @@ run_server_defaults() { for (( i=1; i <= certs_found; i++ )); do echo "${previous_hostcert[i]}" > $HOSTCERT echo "${previous_intermediates[i]}" > $TEMPDIR/intermediatecerts.pem + echo "${previous_hostcert_issuer[i]}" > $TEMPDIR/hostcert_issuer.pem certificate_info "$i" "$certs_found" "${previous_hostcert_txt[i]}" \ "${cipher[i]}" "${keysize[i]}" "${previous_hostcert_type[i]}" \ "${ocsp_response[i]}" "${ocsp_response_status[i]}" "${sni_used[i]}" "${ct[i]}" From fac65d82b0a12f3177429c382515111e847a7abd Mon Sep 17 00:00:00 2001 From: Dirk Date: Wed, 16 May 2018 15:51:45 +0200 Subject: [PATCH 2/5] Add host header to OCSP Request Some OCSP responder seems to need a host header, see e.g. https://blog.ivanristic.com/2014/02/checking-ocsp-revocation-using-openssl.html . This commit adds this header. It addresses not all errors though. E.g. "https://testssl.sh" is fine now, "https://google.com" still returns "Code=400,Reason=Bad Request" which needs further investigation, Also this commit gives a warning if the OCSP request fails (fileout needs to be added) --- testssl.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/testssl.sh b/testssl.sh index 2d0dad9..db2bfde 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1501,8 +1501,8 @@ check_revocation_ocsp() { "$PHONE_OUT" || return 0 tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE - $OPENSSL ocsp -no_nonce -url "$uri" -issuer $TEMPDIR/hostcert_issuer.pem \ - -verify_other $TEMPDIR/intermediatecerts.pem \ + $OPENSSL ocsp -no_nonce -header Host ${uri##http://} -url "$uri" \ + -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ -CAfile $TEMPDIR/intermediatecerts.pem -cert $HOSTCERT &> "$tmpfile" if [[ $? -eq 0 ]] && grep -q "Response verify OK" "$tmpfile"; then if grep -q "$HOSTCERT: good" "$tmpfile"; then @@ -1517,9 +1517,15 @@ check_revocation_ocsp() { outln cat "$tmpfile" fi - elif [[ $DEBUG -ge 2 ]]; then - outln - cat "$tmpfile" + else + out ", " + pr_warning "error querying OCSP responder" + if [[ $DEBUG -ge 2 ]]; then + outln + cat "$tmpfile" + else + out " (--debug >= 2 shows reason)" + fi fi } From 82aae158ba82f41054e3a765970d78661cb1ff73 Mon Sep 17 00:00:00 2001 From: Dirk Date: Wed, 16 May 2018 19:52:10 +0200 Subject: [PATCH 3/5] Minor additions to OCSP revocation check Error from OCSP responder is now being displayed (and logged to JSON, ...) Whole replay is kept in $tmpfile for debugging purposes JSON output added for OCSP responderi query failures Furtermore wget was replaced by "type -p" and grep by fgrep. --- testssl.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/testssl.sh b/testssl.sh index db2bfde..8427544 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1402,10 +1402,10 @@ http_get() { "$SNEAKY" && useragent="$UA_SNEAKY" # automatically handles proxy vars via ENV - if which curl &>/dev/null; then + if type -p curl &>/dev/null; then curl -s -A $''"$useragent"'' -o $dl "$1" return $? - elif which wget &>/dev/null; then + elif type -p wget &>/dev/null; then wget -q -U $''"$useragent"'' -O $dl "$1" return $? else @@ -1432,7 +1432,7 @@ ldap_get() { local tmpfile="$2" local jsonID="$3" - if which curl &>/dev/null; then + if type -p curl &>/dev/null; then ldif="$(curl -s "$crl")" if [[ $? -eq 0 ]]; then awk '/certificateRevocationList/ { print $2 }' <<< "$ldif" | $OPENSSL base64 -d -A -out "$tmpfile" 2>/dev/null @@ -1498,18 +1498,19 @@ check_revocation_ocsp() { local jsonID="$2" local tmpfile="" local -i success + local code="" "$PHONE_OUT" || return 0 tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE $OPENSSL ocsp -no_nonce -header Host ${uri##http://} -url "$uri" \ - -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ - -CAfile $TEMPDIR/intermediatecerts.pem -cert $HOSTCERT &> "$tmpfile" - if [[ $? -eq 0 ]] && grep -q "Response verify OK" "$tmpfile"; then + -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ + -CAfile $TEMPDIR/intermediatecerts.pem -cert $HOSTCERT -text &> "$tmpfile" + if [[ $? -eq 0 ]] && fgrep -q "Response verify OK" "$tmpfile"; then if grep -q "$HOSTCERT: good" "$tmpfile"; then out ", " pr_svrty_good "not revoked" fileout "$jsonID" "OK" "not revoked" - elif grep -q "$HOSTCERT: revoked" "$tmpfile"; then + elif fgrep -q "$HOSTCERT: revoked" "$tmpfile"; then out ", " pr_svrty_critical "revoked" fileout "$jsonID" "CRITICAL" "revoked" @@ -1518,13 +1519,15 @@ check_revocation_ocsp() { cat "$tmpfile" fi else + code="$(awk -F':' '/Code/ { print $NF }' $tmpfile)" out ", " pr_warning "error querying OCSP responder" + fileout "$jsonID" "WARN" "$code" if [[ $DEBUG -ge 2 ]]; then outln cat "$tmpfile" else - out " (--debug >= 2 shows reason)" + out " ($code)" fi fi } From 7d36734a969658628f2d89689552c4e8f55224c0 Mon Sep 17 00:00:00 2001 From: Dirk Date: Fri, 18 May 2018 20:30:37 +0200 Subject: [PATCH 4/5] Handle host header in OCSP request properly My previous commit added a host header but didn't properly format the host header (trailing slashes / path). This commit corrects that so that the 305 times HTTP 400 in #1056 should now be gone (TBC), including Google CA responders. One issue which needs to be addressed (same as in CRL revocation checks): Not trusted certificates (zhanqi.tv, taken from my Alexa scans) fail for obvious reasons. --- testssl.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index 8427544..725cc1a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1499,10 +1499,13 @@ check_revocation_ocsp() { local tmpfile="" local -i success local code="" + local host_header="" "$PHONE_OUT" || return 0 tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE - $OPENSSL ocsp -no_nonce -header Host ${uri##http://} -url "$uri" \ + host_header=${uri##http://} + host_header=${host_header%/*} + $OPENSSL ocsp -no_nonce -header Host ${host_header} -url "$uri" \ -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ -CAfile $TEMPDIR/intermediatecerts.pem -cert $HOSTCERT -text &> "$tmpfile" if [[ $? -eq 0 ]] && fgrep -q "Response verify OK" "$tmpfile"; then @@ -1522,10 +1525,11 @@ check_revocation_ocsp() { code="$(awk -F':' '/Code/ { print $NF }' $tmpfile)" out ", " pr_warning "error querying OCSP responder" + [[ -s "$tmpfile" ]] && code="empty ocsp response" fileout "$jsonID" "WARN" "$code" if [[ $DEBUG -ge 2 ]]; then outln - cat "$tmpfile" + [[ -s "$tmpfile" ]] && cat "$tmpfile" || echo "empty ocsp response" else out " ($code)" fi From 615259297331177f7141eb9165c087f91e8337c6 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 18 May 2018 15:35:10 -0400 Subject: [PATCH 5/5] HTTP URLs with multiple components in path In some cases the OCSP URI contains multiple components in the path (e.g., http://www.example.com/OCSP/myOCSPresponder). This PR changes check_revocation_ocsp() to remove all components in the path, rather than just the final component, when extracting the host name from the URI for the host header. --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 725cc1a..9cba2f1 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1504,7 +1504,7 @@ check_revocation_ocsp() { "$PHONE_OUT" || return 0 tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE host_header=${uri##http://} - host_header=${host_header%/*} + host_header=${host_header%%/*} $OPENSSL ocsp -no_nonce -header Host ${host_header} -url "$uri" \ -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ -CAfile $TEMPDIR/intermediatecerts.pem -cert $HOSTCERT -text &> "$tmpfile"