Merge pull request #1714 from dcooper16/clientauth
Print information about certificate-based client authentication
This commit is contained in:
commit
e9f73ffffd
195
testssl.sh
195
testssl.sh
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue