From 30b93d4c726c3cd668b3bd9c63f5d51dbae860e2 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 4 Oct 2019 16:55:09 -0400 Subject: [PATCH] Use determine_optimal_sockets_params() in run_protocols() This PR modifies run_protocols() to use the information collected by determine_optimal_sockets_params(). If it has already been determined that a protocol is supported, then no test is run. run_protocols() will still run a test for a protocol even if it has been determined that the server does not support that protocol. The reason for running the test is to verify that the server handles version negotiation correctly. This could be a TLSv1 server that rejects a TLSv1.2 or TLSv1.3 ClientHello, or it could happen in the opposite direction. At one point there was a server that would respond to an SSLv3 ClientHello with a TLSv1.2 ServerHello. This PR required a couple of changes to determine_optimal_sockets_params() so that additional information could be passed to run_protocols(). If the server supports TLS 1.3, then run_protocols() needs to know which version (RFC 8446, draft 28, draft 27, etc.) rather than just that TLS 1.3 is supported. If the server supports TLS 1.2, but not TLS 1.3, then run_protocols() needs to know about at least one TLS 1.2 cipher that the server supports so that it can form a TLS 1.3 ClientHello that has no more than 128 ciphers and that should result in the server returning a TLS 1.2 ServerHello. --- testssl.sh | 112 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/testssl.sh b/testssl.sh index 6300d43..632accf 100755 --- a/testssl.sh +++ b/testssl.sh @@ -285,6 +285,7 @@ NR_SOCKET_FAIL=0 # Counter for socket failures NR_OSSL_FAIL=0 # .. for OpenSSL connects NR_HEADER_FAIL=0 # .. for HTTP_GET PROTOS_OFFERED="" # This keeps which protocol is being offered. See has_server_protocol(). +TLS12_CIPHER_OFFERED="" # This contains the hexcode of a cipher known to be supported by the server with TLS 1.2 CURVES_OFFERED="" # This keeps which curves have been detected. Just for error handling KNOWN_OSSL_PROB=false # We need OpenSSL a few times. This variable is an indicator if we can't connect. Eases handling DETECTED_TLS_VERSION="" # .. as hex string, e.g. 0300 or 0303 @@ -4868,7 +4869,7 @@ run_protocols() { local tls13_ciphers_to_test="" local i drafts_offered="" drafts_offered_str="" supported_versions debug_recomm="" local tls12_detected_version - local -i ret=0 ret_val_tls12=0 ret_val_tls13=0 + local -i ret=0 ret_val_ssl3 ret_val_tls1 ret_val_tls11 ret_val_tls12=0 ret_val_tls13=0 local offers_tls13=false local jsonID="SSLv2" @@ -4962,12 +4963,16 @@ run_protocols() { pr_bold " SSLv3 "; jsonID="SSLv3" - if "$using_sockets"; then + if [[ $(has_server_protocol ssl3) -eq 0 ]]; then + ret_val_ssl3=0 + elif "$using_sockets"; then tls_sockets "00" "$TLS_CIPHER" + ret_val_ssl3=$? else run_prototest_openssl "-ssl3" + ret_val_ssl3=$? fi - case $? in + case $ret_val_ssl3 in 0) prln_svrty_high "offered (NOT ok)" fileout "$jsonID" "HIGH" "offered" latest_supported="0300" @@ -5024,12 +5029,16 @@ run_protocols() { pr_bold " TLS 1 "; jsonID="TLS1" - if "$using_sockets"; then + if [[ $(has_server_protocol tls1) -eq 0 ]]; then + ret_val_tls1=0 + elif "$using_sockets"; then tls_sockets "01" "$TLS_CIPHER" + ret_val_tls1=$? else run_prototest_openssl "-tls1" + ret_val_tls1=$? fi - case $? in + case $ret_val_tls1 in 0) pr_svrty_low "offered" ; outln " (deprecated)" fileout "$jsonID" "LOW" "offered (deprecated)" latest_supported="0301" @@ -5098,12 +5107,16 @@ run_protocols() { pr_bold " TLS 1.1 "; jsonID="TLS1_1" - if "$using_sockets"; then + if [[ $(has_server_protocol tls1_1) -eq 0 ]]; then + ret_val_tls11=0 + elif "$using_sockets"; then tls_sockets "02" "$TLS_CIPHER" + ret_val_tls11=$? else run_prototest_openssl "-tls1_1" + ret_val_tls11=$? fi - case $? in + case $ret_val_tls11 in 0) pr_svrty_low "offered" ; outln " (deprecated)" fileout "$jsonID" "LOW" "offered (deprecated)" latest_supported="0302" @@ -5176,35 +5189,36 @@ run_protocols() { # Now, we are doing a basic/pre test for TLS 1.2 and 1.3 in order not to penalize servers (medium) # running TLS 1.3 only when TLS 1.2 is not offered. 0 and 5 are the return codes for # TLS 1.3 support (kind of, including deprecated pre-versions of TLS 1.3) - if "$using_sockets"; then + if [[ $(has_server_protocol tls1_2) -eq 0 ]]; then + ret_val_tls12=0 + elif "$using_sockets"; then tls_sockets "03" "$TLS12_CIPHER" ret_val_tls12=$? tls12_detected_version="$DETECTED_TLS_VERSION" + else + run_prototest_openssl "-tls1_2" + ret_val_tls12=$? + fi + + if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then + ret_val_tls13=0 + elif "$using_sockets"; then # Need to ensure that at most 128 ciphers are included in ClientHello. - # If the TLSv1.2 test was successful, then use the 5 TLSv1.3 ciphers - # plus the cipher selected in the TLSv1.2 test. If the TLSv1.2 test was - # not successful, then just use the 5 TLSv1.3 ciphers plus the list of - # ciphers used in all of the previous tests ($TLS_CIPHER). - if [[ $ret_val_tls12 -eq 0 ]] || [[ $ret_val_tls12 -eq 2 ]]; then - tls13_ciphers_to_test="$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" - if [[ "$tls13_ciphers_to_test" == TLS_* ]] || [[ "$tls13_ciphers_to_test" == SSL_* ]]; then - tls13_ciphers_to_test="$(rfc2hexcode "$tls13_ciphers_to_test")" - else - tls13_ciphers_to_test="$(openssl2hexcode "$tls13_ciphers_to_test")" - fi - fi - if [[ ${#tls13_ciphers_to_test} -eq 9 ]]; then - tls13_ciphers_to_test="$TLS13_CIPHER, ${tls13_ciphers_to_test:2:2},${tls13_ciphers_to_test:7:2}, 00,ff" + # If the TLSv1.2 test in determine_optimal_sockets_params() was successful, + # then use the 5 TLSv1.3 ciphers plus the cipher selected in the TLSv1.2 test. + # If the TLSv1.2 test was not successful, then just use the 5 TLSv1.3 ciphers + # plus the list of ciphers used in all of the previous tests ($TLS_CIPHER). + if [[ -n "$TLS12_CIPHER_OFFERED" ]]; then + tls13_ciphers_to_test="$TLS13_CIPHER, $TLS12_CIPHER_OFFERED, 00,ff" else tls13_ciphers_to_test="$TLS13_CIPHER,$TLS_CIPHER" fi tls_sockets "04" "$tls13_ciphers_to_test" + ret_val_tls13=$? else - run_prototest_openssl "-tls1_2" - ret_val_tls12=$? run_prototest_openssl "-tls1_3" + ret_val_tls13=$? fi - ret_val_tls13=$? if [[ $ret_val_tls13 -eq 0 ]] || [[ $ret_val_tls13 -eq 5 ]]; then offers_tls13=true # This variable comes in handy for further if statements below fi @@ -5300,17 +5314,13 @@ run_protocols() { prln_svrty_best "offered (OK)" fileout "$jsonID" "OK" "offered" else - # Determine which version of TLS 1.3 was offered. For drafts 18-21 the - # version appears in the ProtocolVersion field of the ServerHello. For - # drafts 22-28 and the final TLS 1.3 the ProtocolVersion field contains - # 0303 and the actual version appears in the supported_versions extension. - if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then - drafts_offered+=" ${TLS_SERVER_HELLO:8:4} " - elif [[ "$TLS_SERVER_HELLO" =~ 002B00020304 ]]; then + # If TLS 1.3 is offered, then its support was detected + # by determine_optimal_sockets_params(). + if [[ $(has_server_protocol tls1_3_rfc8446) -eq 0 ]]; then drafts_offered+=" 0304 " else for i in 1C 1B 1A 19 18 17 16 15 14 13 12; do - if [[ "$TLS_SERVER_HELLO" =~ 002B00027F$i ]]; then + if [[ $(has_server_protocol tls1_3_draft$(hex2dec "$i")) -eq 0 ]]; then drafts_offered+=" 7F$i " break fi @@ -5457,8 +5467,9 @@ run_protocols() { [[ $? -ne 0 ]] && exit $ERR_CLUELESS fi - if [[ "$PROTOS_OFFERED" =~ tls1_3:yes ]]; then - if [[ ! "${PROTOS_OFFERED//tls1_3:yes /}" =~ yes ]]; then + if [[ "$(has_server_protocol "tls1_3")" -eq 0 ]]; then + if [[ "$(has_server_protocol "tls1_2")" -ne 0 ]] && [[ "$(has_server_protocol "tls1_1")" -ne 0 ]] && + [[ "$(has_server_protocol "tls1")" -ne 0 ]] && [[ "$(has_server_protocol "ssl3")" -ne 0 ]]; then TLS13_ONLY=true if ! "$HAS_TLS13"; then pr_magenta " $NODE:$PORT appears to support TLS 1.3 ONLY. You better use --openssl=" @@ -17804,8 +17815,8 @@ sclient_auth() { # This information can be used by determine_optimal_proto() to help distinguish between a server # that is not TLS/SSL enabled and one that is not compatible with the version of OpenSSL being used. determine_optimal_sockets_params() { - local -i ret1 ret2 - local proto + local -i ret1=1 ret2=1 + local i proto cipher_offered local all_failed=true # If a STARTTLS protocol is specified and $SSL_NATIVE is true, then skip this test, since @@ -17832,6 +17843,24 @@ determine_optimal_sockets_params() { KEY_SHARE_EXTN_NR="33" fi fi + if ! "$all_failed"; then + # Determine which version of TLS 1.3 was offered. For drafts 18-21 the + # version appears in the ProtocolVersion field of the ServerHello. For + # drafts 22-28 and the final TLS 1.3 the ProtocolVersion field contains + # 0303 and the actual version appears in the supported_versions extension. + if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then + add_tls_offered tls1_3_draft$(hex2dec "${TLS_SERVER_HELLO:10:2}") yes + elif [[ "$TLS_SERVER_HELLO" =~ 002B00020304 ]]; then + add_tls_offered tls1_3_rfc8446 yes + else + for i in 1C 1B 1A 19 18 17 16 15 14 13 12; do + if [[ "$TLS_SERVER_HELLO" =~ 002B00027F$i ]]; then + add_tls_offered tls1_3_draft$(hex2dec "$i") yes + break + fi + done + fi + fi # Need to determine which set of ciphers is best to use with # a TLSv1.2 ClientHello since there are far more than 128 ciphers @@ -17873,6 +17902,15 @@ determine_optimal_sockets_params() { all_failed=false fi fi + if [[ $ret1 -eq 0 ]] || [[ $ret2 -eq 0 ]]; then + cipher_offered="$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" + if [[ "$cipher_offered" == TLS_* ]] || [[ "$cipher_offered" == SSL_* ]]; then + cipher_offered="$(rfc2hexcode "$cipher_offered")" + else + cipher_offered="$(openssl2hexcode "$cipher_offered")" + fi + [[ ${#cipher_offered} -eq 9 ]] && TLS12_CIPHER_OFFERED="${cipher_offered:2:2},${cipher_offered:7:2}" + fi if "$all_failed"; then # One of the following must be true: