From 44787d6bcb8fe9e8bf4d3c0c759d31135184ce4e Mon Sep 17 00:00:00 2001 From: David Cooper Date: Thu, 3 Sep 2020 08:27:32 -0400 Subject: [PATCH 1/4] Extract Client Auth CA list This commit is a first step towards addressing #1709. It attempts to determime whether certificate-based client authentication is (1) not requested, (2) optional, or (3) required. If it is either optional or required, then it extracts the list of CA names (DNs) that the server sends in its CertificateRequest message. The code for extracting the CA list from the CertificateRequest message seems to be working correctly. However, this commit is incomplete for a couple of reasons. First, it does not produce any new output, it just collects the information. Second, sclient_auth() needs some work. The current sclient_auth() simply returns 0 if $OPENSSL returned 0. This may be okay if only trying to determine whether certificate-based client authentication is required. However, if it is optional, then the output will include "CertificateRequest", but $OPENSSL will return 0, since the connection was successful even though the client did not provide a certificates. If $OPENSSL does not return 0, then sclient_auth() checks whether Master-Key is present. This works for TLS 1.2 and earlier, but not for TLS 1.3. So, sclient_auth() needs to be updated to work correctly with TLS 1.3. The modified version of sclient_auth() will set CLIENT_AUTH and CLIENT_AUTH_CA_LIST for any version of TLS, but the remaining part of the code needs work. As I am not clear on the reason for this code, I need some help with it. Why does the code only look for "CertificateRequest" if "Master-Key" is present? Why is there a check for Session-ID in a function that is supposed to just be checking for client authentication. Why is CLIENT_AUTH set to false if SESSION-ID is absent (this is a no-op since CLIENT_AUTH would already have been false)? --- testssl.sh | 136 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 18 deletions(-) diff --git a/testssl.sh b/testssl.sh index fac702d..d977d1b 100755 --- a/testssl.sh +++ b/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="" TMPFILE="" ERRFILE="" -CLIENT_AUTH=false +CLIENT_AUTH="none" +CLIENT_AUTH_CA_LIST="" TLS_TICKETS=false NO_SSL_SESSIONID=false 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() { local -i was_killed - if ! "$CLIENT_AUTH"; then + if [[ "$CLIENT_AUTH" != require ]]; then if ! "$HAS_TLS13" && "$TLS13_ONLY"; then # Using sockets is a lot slower than using OpenSSL, and it is # 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" fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks" ;; - *) if "$CLIENT_AUTH"; then + *) if [[ "$CLIENT_AUTH" == require ]]; then out " certificate-based authentication => skipping all HTTP checks" echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks" @@ -2494,7 +2495,7 @@ run_http_date() { local spaces=" " jsonID="HTTP_clock_skew" - if [[ $SERVICE != HTTP ]] || "$CLIENT_AUTH"; then + if [[ $SERVICE != HTTP ]] || [[ "$CLIENT_AUTH" == require ]]; then return 0 fi if [[ ! -s $HEADERFILE ]]; then @@ -6443,7 +6444,7 @@ sub_session_resumption() { return 1 fi fi - "$CLIENT_AUTH" && return 6 + [[ "$CLIENT_AUTH" == require ]] && return 6 if ! "$HAS_TLS13" && "$HAS_NO_SSL2"; then addcmd+=" -no_ssl2" else @@ -8365,7 +8366,7 @@ certificate_transparency() { fi fi - if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then + if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != require ]]; then # At the moment Certificate Transparency only applies to HTTPS. tm_out "N/A" else @@ -15798,7 +15799,7 @@ run_ticketbleed() { [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln pr_bold " Ticketbleed"; out " ($cve), experiment. " - if [[ "$SERVICE" != HTTP ]] && ! "$CLIENT_AUTH"; then + if [[ "$SERVICE" != HTTP ]] && [[ "$CLIENT_AUTH" != require ]]; then outln "-- (applicable only for HTTPS)" fileout "$jsonID" "INFO" "not applicable, not HTTP" "$cve" "$cwe" return 0 @@ -16128,7 +16129,7 @@ run_renego() { [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers" outln fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" - elif "$CLIENT_AUTH"; then + elif [[ "$CLIENT_AUTH" == require ]]; then prln_warning "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 @@ -16251,14 +16252,14 @@ run_crime() { ret=1 elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then pr_svrty_good "not vulnerable (OK)" - if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then + if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != require ]]; then out " (not using HTTP anyway)" fileout "CRIME_TLS" "OK" "not vulnerable (not using HTTP anyway)" "$cve" "$cwe" else fileout "CRIME_TLS" "OK" "not vulnerable" "$cve" "$cwe" fi else - if [[ $SERVICE == HTTP ]] || "$CLIENT_AUTH"; then + if [[ $SERVICE == HTTP ]] || [[ "$CLIENT_AUTH" == require ]]; then pr_svrty_high "VULNERABLE (NOT ok)" fileout "CRIME_TLS" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint" else @@ -16364,11 +16365,11 @@ run_breach() { local detected_compression="" local get_command="" - [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7 + [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != require ]] && return 7 [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln pr_bold " BREACH"; out " ($cve) " - if "$CLIENT_AUTH"; then + if [[ "$CLIENT_AUTH" == require ]]; then outln "cannot be tested (server side requires x509 authentication)" fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" fi @@ -20356,20 +20357,119 @@ check_proxy() { fi } +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. + cert="A003020102020100300A06082A8648CE3D040302${dn}301E170D3139303830353038333030305A170D3139303830353038333030305A30003019301306072A8648CE3D020106082A8648CE3D030107030200FF" + 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 + 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 + 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 +} + +extract_calist() { + local response="$1" + local is_tls13=false + local certreq calist certtypes sigalgs dn + local calist_string="" + local -i len type + + [[ "$response" =~ CertificateRequest ]] || return 0 + [[ "$response" =~ TLS\ 1.3[\,]?\ Handshake\ \[length\ [0-9a-fA-F]*\]\,\ CertificateRequest ]] && is_tls13=true + + certreq="${response##*CertificateRequest}" + certreq="0d${certreq#*0d}" + certreq="${certreq%%<<<*}" + certreq="$(strip_spaces "$(newline_to_spaces "$certreq")")" + certreq="${certreq:8}" + + 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}" + 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}" + fi + 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 # with client authentication, a server with no SSL session ID switched off # sclient_auth() { - [[ $1 -eq 0 ]] && return 0 # no client auth (CLIENT_AUTH=false is preset globally) + if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg + CLIENT_AUTH="require" + [[ $1 -eq 0 ]] && CLIENT_AUTH="optional" + CLIENT_AUTH_CA_LIST="$(extract_calist "$(< "$2")")" + return 0 + fi + [[ $1 -eq 0 ]] && return 0 if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then # connect succeeded - if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg - CLIENT_AUTH=true - return 0 - fi if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then # do another sanity check to be sure - CLIENT_AUTH=false + CLIENT_AUTH="none" NO_SSL_SESSIONID=true # NO_SSL_SESSIONID is preset globally to false for all other cases return 0 fi From e8a3dce5ad98ce01c07ec4a32d451bf6c73e83d0 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Wed, 21 Oct 2020 07:02:29 -0400 Subject: [PATCH 2/4] sclient_auth() improvements Modify sclient_auth() to use checks similar to sclient_connect_successful() to determine whether the connection attempt was successful. Replace uses of awk and grep with Bash internals string comparisons. --- testssl.sh | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/testssl.sh b/testssl.sh index d977d1b..4bfd8bc 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20357,6 +20357,8 @@ check_proxy() { 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 @@ -20389,6 +20391,9 @@ print_dn() { 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 @@ -20396,15 +20401,18 @@ extract_calist() { local calist_string="" local -i len type - [[ "$response" =~ CertificateRequest ]] || return 0 - [[ "$response" =~ TLS\ 1.3[\,]?\ Handshake\ \[length\ [0-9a-fA-F]*\]\,\ CertificateRequest ]] && is_tls13=true + # 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>; @@ -20443,6 +20451,7 @@ extract_calist() { len=2*$(hex2dec "${certreq:0:4}") calist="${certreq:4}" fi + # Convert each DN to a string. while true; do [[ -z "$calist" ]] && break len=2*$(hex2dec "${calist:0:4}") @@ -20459,23 +20468,36 @@ extract_calist() { # with client authentication, a server with no SSL session ID switched off # sclient_auth() { - if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg - CLIENT_AUTH="require" - [[ $1 -eq 0 ]] && CLIENT_AUTH="optional" - CLIENT_AUTH_CA_LIST="$(extract_calist "$(< "$2")")" - return 0 - fi - [[ $1 -eq 0 ]] && return 0 - if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then # connect succeeded - if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session - if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then # do another sanity check to be sure + local server_hello="$(cat -v "$2")" + local re='Master-Key: ([^\ +]*)' + 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="require" + [[ $1 -eq 0 ]] && CLIENT_AUTH="optional" + CLIENT_AUTH_CA_LIST="$(extract_calist "$server_hello")" + return 0 + fi + [[ $1 -eq 0 ]] && return 0 + if [[ ! "$server_hello" =~ Session-ID:\ [a-fA-F0-9]{2,64} ]]; then # probably no SSL session + # 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 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 } From 9dbb629154420799b3143d9a2ff89ad60b6e82c7 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Mon, 25 Jan 2021 11:08:44 -0500 Subject: [PATCH 3/4] Add printing of information about client authentication to run_server_defaults(). Minor cleanup of code to extract information about client authentication. --- testssl.sh | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/testssl.sh b/testssl.sh index 4bfd8bc..fc897ea 100755 --- a/testssl.sh +++ b/testssl.sh @@ -2224,7 +2224,7 @@ s_client_options() { service_detection() { local -i was_killed - if [[ "$CLIENT_AUTH" != require ]]; then + if [[ "$CLIENT_AUTH" != required ]]; then if ! "$HAS_TLS13" && "$TLS13_ONLY"; then # Using sockets is a lot slower than using OpenSSL, and it is # not as reliable, but if OpenSSL can't connect to the server, @@ -2273,7 +2273,7 @@ service_detection() { out " $SERVICE, thus skipping HTTP specific checks" fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks" ;; - *) if [[ "$CLIENT_AUTH" == require ]]; then + *) if [[ "$CLIENT_AUTH" == required ]]; then out " certificate-based authentication => skipping all HTTP checks" echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks" @@ -2495,7 +2495,7 @@ run_http_date() { local spaces=" " jsonID="HTTP_clock_skew" - if [[ $SERVICE != HTTP ]] || [[ "$CLIENT_AUTH" == require ]]; then + if [[ $SERVICE != HTTP ]] || [[ "$CLIENT_AUTH" == required ]]; then return 0 fi if [[ ! -s $HEADERFILE ]]; then @@ -6444,7 +6444,7 @@ sub_session_resumption() { return 1 fi fi - [[ "$CLIENT_AUTH" == require ]] && return 6 + [[ "$CLIENT_AUTH" == required ]] && return 6 if ! "$HAS_TLS13" && "$HAS_NO_SSL2"; then addcmd+=" -no_ssl2" else @@ -8366,7 +8366,7 @@ certificate_transparency() { fi fi - if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != require ]]; then + if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then # At the moment Certificate Transparency only applies to HTTPS. tm_out "N/A" else @@ -9494,7 +9494,7 @@ run_server_defaults() { local -a ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct local -a ciphers_to_test certificate_type 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 "$SSL_NATIVE" && using_sockets=false @@ -9842,6 +9842,26 @@ run_server_defaults() { 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 # no cipher suites specified here. We just want the default vhost subject if ! "$HAS_TLS13" && [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then @@ -15799,7 +15819,7 @@ run_ticketbleed() { [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln pr_bold " Ticketbleed"; out " ($cve), experiment. " - if [[ "$SERVICE" != HTTP ]] && [[ "$CLIENT_AUTH" != require ]]; then + if [[ "$SERVICE" != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then outln "-- (applicable only for HTTPS)" fileout "$jsonID" "INFO" "not applicable, not HTTP" "$cve" "$cwe" return 0 @@ -16129,7 +16149,7 @@ run_renego() { [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers" outln fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" - elif [[ "$CLIENT_AUTH" == require ]]; then + elif [[ "$CLIENT_AUTH" == required ]]; then prln_warning "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 @@ -16252,14 +16272,14 @@ run_crime() { ret=1 elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then pr_svrty_good "not vulnerable (OK)" - if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != require ]]; then + if [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then out " (not using HTTP anyway)" fileout "CRIME_TLS" "OK" "not vulnerable (not using HTTP anyway)" "$cve" "$cwe" else fileout "CRIME_TLS" "OK" "not vulnerable" "$cve" "$cwe" fi else - if [[ $SERVICE == HTTP ]] || [[ "$CLIENT_AUTH" == require ]]; then + if [[ $SERVICE == HTTP ]] || [[ "$CLIENT_AUTH" == required ]]; then pr_svrty_high "VULNERABLE (NOT ok)" fileout "CRIME_TLS" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint" else @@ -16365,11 +16385,11 @@ run_breach() { local detected_compression="" local get_command="" - [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != require ]] && return 7 + [[ $SERVICE != HTTP ]] && [[ "$CLIENT_AUTH" != required ]] && return 7 [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln pr_bold " BREACH"; out " ($cve) " - if [[ "$CLIENT_AUTH" == require ]]; then + if [[ "$CLIENT_AUTH" == required ]]; then outln "cannot be tested (server side requires x509 authentication)" fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" fi @@ -20397,7 +20417,7 @@ print_dn() { extract_calist() { local response="$1" local is_tls13=false - local certreq calist certtypes sigalgs dn + local certreq calist="" certtypes sigalgs dn local calist_string="" local -i len type @@ -20430,7 +20450,7 @@ extract_calist() { # This is the certificate_authorities extension calist="${certreq:8:len}" len=2*$(hex2dec "${calist:0:4}") - calist="${calist:4}" + calist="${calist:4:len}" break fi certreq="${certreq:$((len+8))}" @@ -20449,7 +20469,7 @@ extract_calist() { sigalgs="${certreq:4:len}" certreq="${certreq:$((len+4))}" len=2*$(hex2dec "${certreq:0:4}") - calist="${certreq:4}" + calist="${certreq:4:len}" fi # Convert each DN to a string. while true; do @@ -20482,7 +20502,7 @@ sclient_auth() { 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="require" + CLIENT_AUTH="required" [[ $1 -eq 0 ]] && CLIENT_AUTH="optional" CLIENT_AUTH_CA_LIST="$(extract_calist "$server_hello")" return 0 From f829c8ebf0cd1cf4f19ecd04afb1711acbaf44ab Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 5 Feb 2021 13:35:54 -0500 Subject: [PATCH 4/4] Add more comments to print_dn() This commit adds some more explanatory comments to the code in print_dn() --- testssl.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/testssl.sh b/testssl.sh index fc897ea..3b7281c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20387,7 +20387,12 @@ print_dn() { # 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" @@ -20396,6 +20401,10 @@ print_dn() { 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 @@ -20405,6 +20414,8 @@ print_dn() { 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")"