Merge pull request #1714 from dcooper16/clientauth

Print information about certificate-based client authentication
This commit is contained in:
Dirk Wetter 2021-02-08 09:22:31 +01:00 committed by GitHub
commit e9f73ffffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 175 additions and 22 deletions

View File

@ -277,7 +277,8 @@ declare -r ALPN_PROTOs="h2 spdy/3.1 http/1.1 grpc-exp h2-fb spdy/1 spdy/2 spdy/3
TEMPDIR="" TEMPDIR=""
TMPFILE="" TMPFILE=""
ERRFILE="" ERRFILE=""
CLIENT_AUTH=false CLIENT_AUTH="none"
CLIENT_AUTH_CA_LIST=""
TLS_TICKETS=false TLS_TICKETS=false
NO_SSL_SESSIONID=false NO_SSL_SESSIONID=false
CERT_COMPRESSION=${CERT_COMPRESSION:-false} # secret flag to set in addition to --devel for certificate compression CERT_COMPRESSION=${CERT_COMPRESSION:-false} # secret flag to set in addition to --devel for certificate compression
@ -2223,7 +2224,7 @@ s_client_options() {
service_detection() { service_detection() {
local -i was_killed local -i was_killed
if ! "$CLIENT_AUTH"; then if [[ "$CLIENT_AUTH" != required ]]; then
if ! "$HAS_TLS13" && "$TLS13_ONLY"; then if ! "$HAS_TLS13" && "$TLS13_ONLY"; then
# Using sockets is a lot slower than using OpenSSL, and it is # Using sockets is a lot slower than using OpenSSL, and it is
# not as reliable, but if OpenSSL can't connect to the server, # not as reliable, but if OpenSSL can't connect to the server,
@ -2272,7 +2273,7 @@ service_detection() {
out " $SERVICE, thus skipping HTTP specific checks" out " $SERVICE, thus skipping HTTP specific checks"
fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks" fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks"
;; ;;
*) if "$CLIENT_AUTH"; then *) if [[ "$CLIENT_AUTH" == required ]]; then
out " certificate-based authentication => skipping all HTTP checks" out " certificate-based authentication => skipping all HTTP checks"
echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE
fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks" fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks"
@ -2494,7 +2495,7 @@ run_http_date() {
local spaces=" " local spaces=" "
jsonID="HTTP_clock_skew" jsonID="HTTP_clock_skew"
if [[ $SERVICE != HTTP ]] || "$CLIENT_AUTH"; then if [[ $SERVICE != HTTP ]] || [[ "$CLIENT_AUTH" == required ]]; then
return 0 return 0
fi fi
if [[ ! -s $HEADERFILE ]]; then if [[ ! -s $HEADERFILE ]]; then
@ -6443,7 +6444,7 @@ sub_session_resumption() {
return 1 return 1
fi fi
fi fi
"$CLIENT_AUTH" && return 6 [[ "$CLIENT_AUTH" == required ]] && return 6
if ! "$HAS_TLS13" && "$HAS_NO_SSL2"; then if ! "$HAS_TLS13" && "$HAS_NO_SSL2"; then
addcmd+=" -no_ssl2" addcmd+=" -no_ssl2"
else else
@ -8365,7 +8366,7 @@ certificate_transparency() {
fi fi
fi fi
if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then
# At the moment Certificate Transparency only applies to HTTPS. # At the moment Certificate Transparency only applies to HTTPS.
tm_out "N/A" tm_out "N/A"
else else
@ -9493,7 +9494,7 @@ run_server_defaults() {
local -a ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct local -a ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct
local -a ciphers_to_test certificate_type local -a ciphers_to_test certificate_type
local -a -i success local -a -i success
local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions client_auth_ca
local using_sockets=true local using_sockets=true
"$SSL_NATIVE" && using_sockets=false "$SSL_NATIVE" && using_sockets=false
@ -9841,6 +9842,26 @@ run_server_defaults() {
tls_time tls_time
jsonID="clientAuth"
pr_bold " Client Authentication "
outln "$CLIENT_AUTH"
fileout "$jsonID" "INFO" "$CLIENT_AUTH"
if [[ "$CLIENT_AUTH" != none ]]; then
jsonID="clientAuth_CA_list"
pr_bold " CA List for Client Auth "
out_row_aligned "$CLIENT_AUTH_CA_LIST" " "
if [[ "$CLIENT_AUTH_CA_LIST" == empty ]] || [[ $(count_lines "$CLIENT_AUTH_CA_LIST") -eq 1 ]]; then
fileout "$jsonID" "INFO" "$CLIENT_AUTH_CA_LIST"
else
i=1
while read client_auth_ca; do
fileout "$jsonID #$i" "INFO" "$client_auth_ca"
i+=1
done <<< "$CLIENT_AUTH_CA_LIST"
fi
fi
if [[ -n "$SNI" ]] && [[ $certs_found -ne 0 ]] && [[ ! -e $HOSTCERT.nosni ]]; then if [[ -n "$SNI" ]] && [[ $certs_found -ne 0 ]] && [[ ! -e $HOSTCERT.nosni ]]; then
# no cipher suites specified here. We just want the default vhost subject # no cipher suites specified here. We just want the default vhost subject
if ! "$HAS_TLS13" && [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then if ! "$HAS_TLS13" && [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then
@ -15798,7 +15819,7 @@ run_ticketbleed() {
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln
pr_bold " Ticketbleed"; out " ($cve), experiment. " pr_bold " Ticketbleed"; out " ($cve), experiment. "
if [[ "$SERVICE" != HTTP ]] && ! "$CLIENT_AUTH"; then if [[ "$SERVICE" != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then
outln "-- (applicable only for HTTPS)" outln "-- (applicable only for HTTPS)"
fileout "$jsonID" "INFO" "not applicable, not HTTP" "$cve" "$cwe" fileout "$jsonID" "INFO" "not applicable, not HTTP" "$cve" "$cwe"
return 0 return 0
@ -16128,7 +16149,7 @@ run_renego() {
[[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers" [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers"
outln outln
fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe"
elif "$CLIENT_AUTH"; then elif [[ "$CLIENT_AUTH" == required ]]; then
prln_warning "client x509-based authentication prevents this from being tested" prln_warning "client x509-based authentication prevents this from being tested"
fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested"
sec_client_renego=1 sec_client_renego=1
@ -16251,14 +16272,14 @@ run_crime() {
ret=1 ret=1
elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then
pr_svrty_good "not vulnerable (OK)" pr_svrty_good "not vulnerable (OK)"
if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then
out " (not using HTTP anyway)" out " (not using HTTP anyway)"
fileout "CRIME_TLS" "OK" "not vulnerable (not using HTTP anyway)" "$cve" "$cwe" fileout "CRIME_TLS" "OK" "not vulnerable (not using HTTP anyway)" "$cve" "$cwe"
else else
fileout "CRIME_TLS" "OK" "not vulnerable" "$cve" "$cwe" fileout "CRIME_TLS" "OK" "not vulnerable" "$cve" "$cwe"
fi fi
else else
if [[ $SERVICE == HTTP ]] || "$CLIENT_AUTH"; then if [[ $SERVICE == HTTP ]] || [[ "$CLIENT_AUTH" == required ]]; then
pr_svrty_high "VULNERABLE (NOT ok)" pr_svrty_high "VULNERABLE (NOT ok)"
fileout "CRIME_TLS" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint" fileout "CRIME_TLS" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint"
else else
@ -16364,11 +16385,11 @@ run_breach() {
local detected_compression="" local detected_compression=""
local get_command="" local get_command=""
[[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7 [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != required ]] && return 7
[[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln
pr_bold " BREACH"; out " ($cve) " pr_bold " BREACH"; out " ($cve) "
if "$CLIENT_AUTH"; then if [[ "$CLIENT_AUTH" == required ]]; then
outln "cannot be tested (server side requires x509 authentication)" outln "cannot be tested (server side requires x509 authentication)"
fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe"
fi fi
@ -20356,26 +20377,158 @@ check_proxy() {
fi fi
} }
# Given the ASCII-HEX of a DER-encoded distinguished name, return the string
# representation of the name.
print_dn() {
local dn="$1"
local cert name
local -i len
# Use $OPENSSL to print the DN by creating a certificate containing the DN
# as the issuer and then having $OPENSSL print the issuer field in the
# resulting certificate.
# Create the to-be-signed portion of the certificate: version || serialNumber || signature || issuer || validity || subject || subjectPublicKeyInfo
# with the DN to be printed being the issuer.
cert="A003020102020100300A06082A8648CE3D040302${dn}301E170D3139303830353038333030305A170D3139303830353038333030305A30003019301306072A8648CE3D020106082A8648CE3D030107030200FF"
# Make a SEQUENCE of the to-be-signed portion of the certificate.
len=$((${#cert}/2))
if [[ $len -lt 128 ]]; then
cert="30$(printf "%02x" $len)$cert"
elif [[ $len -lt 256 ]]; then
cert="3081$(printf "%02x" $len)$cert"
else
cert="3082$(printf "%04x" $len)$cert"
fi
# Append a signature algorithm and signature value to the end of the
# to-be-signed portion of the certificate and then make a SEQUENCE of
# the result.
cert+="300A06082A8648CE3D040302030200FF"
len=$((${#cert}/2))
if [[ $len -lt 128 ]]; then
cert="30$(printf "%02x" $len)$cert"
elif [[ $len -lt 256 ]]; then
cert="3081$(printf "%02x" $len)$cert"
else
cert="3082$(printf "%04x" $len)$cert"
fi
# Use the LDAP String Representation of Distinguished Names (RFC 2253),
# The current specification is in RFC 4514.
name="$(asciihex_to_binary "$cert" | $OPENSSL x509 -issuer -noout -inform DER -nameopt RFC2253 2>/dev/null)"
name="${name#issuer=}"
tm_out "$(strip_leading_space "$name")"
return 0
}
# Given the OpenSSL output of a response from a TLS server (with the -msg option)
# in which the response includes a CertificateRequest message, return the list of
# distinguished names that are in the CA list.
extract_calist() {
local response="$1"
local is_tls13=false
local certreq calist="" certtypes sigalgs dn
local calist_string=""
local -i len type
# Determine whether this is a TLS 1.3 response, since the information
# is encoded in a different place for TLS 1.3.
[[ "$response" =~ \<\<\<\ TLS\ 1.3[\,]?\ Handshake\ \[length\ [0-9a-fA-F]*\]\,\ CertificateRequest ]] && is_tls13=true
# Extract just the CertificateRequest message as an ASCII-HEX string.
certreq="${response##*CertificateRequest}"
certreq="0d${certreq#*0d}"
certreq="${certreq%%<<<*}"
certreq="$(strip_spaces "$(newline_to_spaces "$certreq")")"
certreq="${certreq:8}"
# Get the list of DNs from the CertificateRequest message.
if "$is_tls13"; then
# struct {
# opaque certificate_request_context<0..2^8-1>;
# Extension extensions<2..2^16-1>;
# } CertificateRequest;
len=2*$(hex2dec "${certreq:0:2}")
certreq="${certreq:$((len+2))}"
len=2*$(hex2dec "${certreq:0:4}")
certreq="${certreq:4}"
while true; do
[[ -z "$certreq" ]] && break
type=$(hex2dec "${certreq:0:4}")
len=2*$(hex2dec "${certreq:4:4}")
if [[ $type -eq 47 ]]; then
# This is the certificate_authorities extension
calist="${certreq:8:len}"
len=2*$(hex2dec "${calist:0:4}")
calist="${calist:4:len}"
break
fi
certreq="${certreq:$((len+8))}"
done
else
# struct {
# ClientCertificateType certificate_types<1..2^8-1>;
# SignatureAndHashAlgorithm
# supported_signature_algorithms<2^16-1>;
# DistinguishedName certificate_authorities<0..2^16-1>;
# } CertificateRequest;
len=2*$(hex2dec "${certreq:0:2}")
certtypes="${certreq:2:len}"
certreq="${certreq:$((len+2))}"
len=2*$(hex2dec "${certreq:0:4}")
sigalgs="${certreq:4:len}"
certreq="${certreq:$((len+4))}"
len=2*$(hex2dec "${certreq:0:4}")
calist="${certreq:4:len}"
fi
# Convert each DN to a string.
while true; do
[[ -z "$calist" ]] && break
len=2*$(hex2dec "${calist:0:4}")
dn="${calist:4:len}"
calist_string+="$(print_dn "$dn")\n"
calist="${calist:$((len+4))}"
done
[[ -z "$calist_string" ]] && calist_string="empty"
tm_out "$calist_string"
return 0
}
# this is only being called from determine_optimal_proto in order to check whether we have a server # this is only being called from determine_optimal_proto in order to check whether we have a server
# with client authentication, a server with no SSL session ID switched off # with client authentication, a server with no SSL session ID switched off
# #
sclient_auth() { sclient_auth() {
[[ $1 -eq 0 ]] && return 0 # no client auth (CLIENT_AUTH=false is preset globally) local server_hello="$(cat -v "$2")"
if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then # connect succeeded local re='Master-Key: ([^\
if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg ]*)'
CLIENT_AUTH=true local connect_success=false
[[ $1 -eq 0 ]] && connect_success=true
! "$connect_success" && [[ "$server_hello" =~ $re ]] && \
[[ -n "${BASH_REMATCH[1]}" ]] && connect_success=true
! "$connect_success" && \
[[ "$server_hello" =~ (New|Reused)\,\ (SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)\,\ Cipher\ is\ ([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]] && \
connect_success=true
if "$connect_success"; then
if [[ "$server_hello" =~ \<\<\<\ (SSL\ [23]|TLS\ 1)(\.[0-3])?[\,]?\ Handshake\ \[length\ [0-9a-fA-F]*\]\,\ CertificateRequest ]]; then
# CertificateRequest message in -msg
CLIENT_AUTH="required"
[[ $1 -eq 0 ]] && CLIENT_AUTH="optional"
CLIENT_AUTH_CA_LIST="$(extract_calist "$server_hello")"
return 0 return 0
fi fi
if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session [[ $1 -eq 0 ]] && return 0
if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then # do another sanity check to be sure if [[ ! "$server_hello" =~ Session-ID:\ [a-fA-F0-9]{2,64} ]]; then # probably no SSL session
CLIENT_AUTH=false # do another sanity check to be sure
if [[ "$server_hello" =~ \-\-\-BEGIN\ CERTIFICATE\-\-\-.*\-\-\-END\ CERTIFICATE\-\-\- ]]; then
CLIENT_AUTH="none"
NO_SSL_SESSIONID=true # NO_SSL_SESSIONID is preset globally to false for all other cases NO_SSL_SESSIONID=true # NO_SSL_SESSIONID is preset globally to false for all other cases
return 0 return 0
fi fi
fi fi
fi fi
# what's left now is: master key empty, handshake returned not successful, session ID empty --> not successful # what's left now is: no protocol and ciphersuite specified, handshake returned not successful, session ID empty --> not successful
return 1 return 1
} }