From 06e7205687b570b74ce77f17966c085367a7c730 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Mon, 18 Apr 2022 13:08:32 -0400 Subject: [PATCH] Support OpenSSL with no TLSv1 RFC 8996, Deprecating TLS 1.0 and TLS 1.1, states that TLS clients MUST NOT send a TLS 1.0 or TLS 1.1 ClientHello and MUST respond to a TLS 1.0 or TLS 1.1 ServerHello with a "protocol_version" alert. At the moment, all versions of OpenSSL support TLS 1.0, TLS 1.1, and TLS 1.2. However, TLS 1.0 and TLS 1.1 are disabled in LibreSSL 3.8.1 and it is possible to compile OpenSSL without support for these protocols (using the configure options no-tls1, no-tls1_1, and no-tls1_2). This commit adds support for versions of $OPENSSL that do not support TLS 1.0 or TLS 1.1. --- testssl.sh | 285 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 176 insertions(+), 109 deletions(-) diff --git a/testssl.sh b/testssl.sh index 31f1f85..3de66c0 100755 --- a/testssl.sh +++ b/testssl.sh @@ -328,6 +328,9 @@ HAS_CURVES=false OSSL_SUPPORTED_CURVES="" HAS_SSL2=false HAS_SSL3=false +HAS_TLS1=false +HAS_TLS11=false +HAS_TLS12=false HAS_TLS13=false HAS_X448=false HAS_X25519=false @@ -976,6 +979,7 @@ actually_supported_osslciphers() { else options="${options//-no_ssl2 /}" fi + ! "$HAS_TLS1" && options="${options//-tls1 /}" if "$HAS_CIPHERSUITES"; then $OPENSSL ciphers $options $OSSL_CIPHERS_S -ciphersuites "$tls13_ciphers" "$ciphers" 2>/dev/null || echo "" elif [[ -n "$tls13_ciphers" ]]; then @@ -3880,11 +3884,10 @@ run_cipher_match(){ [[ $((nr_ossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1 fi - if "$HAS_TLS13"; then - protos_to_try="-no_ssl2 -tls1_2 -tls1_1 -tls1" - else - protos_to_try="-no_ssl2 -tls1_1 -tls1" - fi + protos_to_try="-no_ssl2" + "$HAS_TLS13" && "$HAS_TLS12" && protos_to_try+=" -tls1_2" + "$HAS_TLS11" && protos_to_try+=" -tls1_1" + "$HAS_TLS1" && protos_to_try+=" -tls1" "$HAS_SSL3" && protos_to_try+=" -ssl3" for proto in $protos_to_try; do @@ -4153,11 +4156,10 @@ run_allciphers() { [[ $((nr_ossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1 fi - if "$HAS_TLS13"; then - protos_to_try="-no_ssl2 -tls1_2 -tls1_1 -tls1" - else - protos_to_try="-no_ssl2 -tls1_1 -tls1" - fi + protos_to_try="-no_ssl2" + "$HAS_TLS13" && "$HAS_TLS12" && protos_to_try+=" -tls1_2" + "$HAS_TLS11" && protos_to_try+=" -tls1_1" + "$HAS_TLS1" && protos_to_try+=" -tls1" "$HAS_SSL3" && protos_to_try+=" -ssl3" for proto in $protos_to_try; do @@ -4315,7 +4317,7 @@ ciphers_by_strength() { "$wide" || out " " if ! "$using_sockets" && ! sclient_supported "$proto"; then "$wide" && outln - pr_local_problem "$OPENSSL does not support $proto" + pr_local_problem "Your $OPENSSL does not support $proto" "$wide" && outln return 0 fi @@ -4445,7 +4447,7 @@ ciphers_by_strength() { fi else # no SSLv2 nr_ossl_ciphers=0 - if { "$HAS_SSL3" || [[ $proto != -ssl3 ]]; } && { "$HAS_TLS13" || [[ $proto != -tls1_3 ]]; }; then + if sclient_supported "$proto"; then for (( i=0; i < nr_ciphers; i++ )); do if "${ossl_supported[i]}"; then ciphers_found2[nr_ossl_ciphers]=false @@ -5110,10 +5112,42 @@ run_client_simulation() { [[ -n "$supported_curves" ]] && curves[i]="-curves ${supported_curves:1}" fi options="$(s_client_options "-cipher ${ch_ciphers[i]} -ciphersuites "\'${ciphersuites[i]}\'" ${curves[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${ch_sni[i]}")" + "$HAS_TLS12" || options="${options//-no_tls1_2 /}" + "$HAS_TLS11" || options="${options//-no_tls1_1 /}" + "$HAS_TLS1" || options="${options//-no_tls1 /}" + "$HAS_SSL3" || options="${options//-no_ssl3 /}" debugme echo "$OPENSSL s_client $options $TMPFILE 2>$ERRFILE - sclient_connect_successful $? $TMPFILE - sclient_success=$? + # If "${protos[i]}" specifies protocols that aren't supported + # by $OPENSSL, then skip the test. + if [[ ! "${protos[i]}" =~ -no_ ]] && [[ ! "${protos[i]}" =~ \ ]] && ! sclient_supported "${protos[i]}"; then + pr_local_problem "${protos[i]} not supported, " + sclient_success=1 + elif ! "$HAS_SSL3" && [[ "${highest_protocol[i]}" == 0x0300 ]]; then + pr_local_problem "SSLv3 not supported, " + sclient_success=1 + elif ! "$HAS_TLS1" && [[ "${highest_protocol[i]}" == 0x0301 ]]; then + pr_local_problem "TLS 1 not supported, " + sclient_success=1 + elif ! "$HAS_TLS11" && [[ "${highest_protocol[i]}" == 0x0302 ]]; then + pr_local_problem "TLS 1.1 not supported, " + sclient_success=1 + elif ! "$HAS_TLS12" && [[ "${highest_protocol[i]}" == 0x0303 ]]; then + pr_local_problem "TLS 1.2 not supported, " + sclient_success=1 + elif ! "$HAS_TLS13" && [[ "${highest_protocol[i]}" == 0x0304 ]]; then + pr_local_problem "TLS 1.3 not supported, " + sclient_success=1 + elif [[ -z "$(actually_supported_osslciphers ${ch_ciphers[i]} ${ciphersuites[i]})" ]]; then + # In some cases $OPENSSL supports the protocol, but none of the ciphers + # offered by the client being simulated. In that case, issue a "Local problem" + # rather than having sclient_connect_successful() write "Oops: openssl s_client connect problem". + pr_local_problem "No supported ciphers, " + sclient_success=1 + else + $OPENSSL s_client $options $TMPFILE 2>$ERRFILE + sclient_connect_successful $? $TMPFILE + sclient_success=$? + fi fi if [[ $sclient_success -eq 0 ]]; then # If an ephemeral DH key was used, check that the number of bits is within range. @@ -5149,13 +5183,13 @@ run_client_simulation() { if [[ "$proto" == TLSv1.2 ]] && { ! "$using_sockets" || [[ -z "${handshakebytes[i]}" ]]; }; then # OpenSSL reports TLS1.2 even if the connection is TLS1.1 or TLS1.0. Need to figure out which one it is... for tls in ${tlsvers[i]}; do - # If the handshake data includes TLS 1.3 we need to remove it, otherwise the + # If the handshake data specifies an unsupported protocol we need to remove it, otherwise the # simulation will fail with # 'Oops: openssl s_client connect problem' # before/after trying another protocol. We only print a warning it in debug mode # as otherwise we would need e.g. handle the curves in a similar fashion -- not # to speak about ciphers - if [[ $tls =~ 1_3 ]] && ! "$HAS_TLS13"; then - debugme pr_local_problem "TLS 1.3 not supported, " + if ! sclient_supported "$tls"; then + debugme pr_local_problem "$tls not supported, " continue fi options="$(s_client_options "$tls -cipher ${ch_ciphers[i]} -ciphersuites "\'${ciphersuites[i]}\'" ${curves[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${ch_sni[i]}")" @@ -5228,7 +5262,6 @@ run_client_simulation() { } # generic function whether $1 is supported by s_client. -# Currently only used for protocols that's why we saved -connect $NXCONNECT. sclient_supported() { case "$1" in -ssl2) @@ -5237,10 +5270,19 @@ sclient_supported() { -ssl3) "$HAS_SSL3" || return 7 ;; + -tls1) + "$HAS_TLS1" || return 7 + ;; + -tls1_1) + "$HAS_TLS11" || return 7 + ;; + -tls1_2) + "$HAS_TLS12" || return 7 + ;; -tls1_3) "$HAS_TLS13" || return 7 ;; - *) if $OPENSSL s_client "$1" &1 | grep -aiq "unknown option"; then + *) if $OPENSSL s_client -connect $NXCONNECT "$1" &1 | grep -aiq "unknown option"; then return 7 fi ;; @@ -5248,20 +5290,6 @@ sclient_supported() { return 0 } -# generic function whether $1 is supported by s_client ($2: string to display) -#TODO: we need to consider to remove the two instances from where this is called. -# -locally_supported() { - local -i ret - - [[ -n "$2" ]] && out "$2 " - sclient_supported "$1" - ret=$? - [[ $ret -eq 7 ]] && prln_local_problem "$OPENSSL doesn't support \"s_client $1\"" - return $ret -} - - # The protocol check in run_protocols needs to be redone. The using_sockets part there kind of sucks. # 1) we need to have a variable where the results are being stored so that every other test doesn't have to do this again # --> we have that but certain information like "downgraded" are not being passed. That's not ok for run_protocols()/ @@ -5573,8 +5601,10 @@ run_protocols() { case $ret_val_tls1 in 0) pr_svrty_low "offered" ; outln " (deprecated)" fileout "$jsonID" "LOW" "offered (deprecated)" - latest_supported="0301" - latest_supported_string="TLSv1.0" + if "$using_sockets" || "$HAS_TLS1"; then + latest_supported="0301" + latest_supported_string="TLSv1.0" + fi add_proto_offered tls1 yes set_grade_cap "B" "TLS 1.0 offered" ;; # nothing wrong with it -- per se @@ -5653,8 +5683,10 @@ run_protocols() { case $ret_val_tls11 in 0) pr_svrty_low "offered" ; outln " (deprecated)" fileout "$jsonID" "LOW" "offered (deprecated)" - latest_supported="0302" - latest_supported_string="TLSv1.1" + if "$using_sockets" || "$HAS_TLS11"; then + latest_supported="0302" + latest_supported_string="TLSv1.1" + fi add_proto_offered tls1_1 yes set_grade_cap "B" "TLS 1.1 offered" ;; # nothing wrong with it @@ -5766,8 +5798,10 @@ run_protocols() { case $ret_val_tls12 in 0) prln_svrty_best "offered (OK)" fileout "$jsonID" "OK" "offered" - latest_supported="0303" - latest_supported_string="TLSv1.2" + if "$using_sockets" || "$HAS_TLS12"; then + latest_supported="0303" + latest_supported_string="TLSv1.2" + fi add_proto_offered tls1_2 yes ;; # GCM cipher in TLS 1.2: very good! 1) add_proto_offered tls1_2 no @@ -6012,15 +6046,17 @@ listciphers() { local debugname="" local ciphers="$1" local tls13_ciphers="$TLS13_OSSL_CIPHERS" + local options="$3 " [[ "$2" != ALL ]] && tls13_ciphers="$2" "$HAS_SECLEVEL" && [[ -n "$ciphers" ]] && ciphers="@SECLEVEL=0:$1" + ! "$HAS_TLS1" && options="${options//-tls1 /}" if "$HAS_CIPHERSUITES"; then - $OPENSSL ciphers $OSSL_CIPHERS_S $3 -ciphersuites "$tls13_ciphers" "$ciphers" &>$TMPFILE + $OPENSSL ciphers $OSSL_CIPHERS_S $options -ciphersuites "$tls13_ciphers" "$ciphers" &>$TMPFILE elif [[ -n "$tls13_ciphers" ]]; then - $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$tls13_ciphers:$ciphers" &>$TMPFILE + $OPENSSL ciphers $OSSL_CIPHERS_S $options "$tls13_ciphers:$ciphers" &>$TMPFILE else - $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$ciphers" &>$TMPFILE + $OPENSSL ciphers $OSSL_CIPHERS_S $options "$ciphers" &>$TMPFILE fi ret=$? debugme cat $TMPFILE @@ -6066,8 +6102,8 @@ sub_cipherlists() { ! "$HAS_TLS13" && continue [[ -z "$2" ]] && continue fi - ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue if [[ "$proto" != -no_ssl2 ]]; then + sclient_supported "$proto" || continue "$FAST" && continue [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue fi @@ -7161,18 +7197,13 @@ cipher_pref_check() { local -a -i index local ciphers_found_with_sockets=false prioritize_chacha=false - if [[ $proto == ssl3 ]] && ! "$HAS_SSL3" && ! "$using_sockets"; then + if ! "$using_sockets" && ! sclient_supported "-$proto"; then outln - prln_local_problem "$OPENSSL doesn't support \"s_client -ssl3\""; - return 0 - fi - if [[ $proto == tls1_3 ]] && ! "$HAS_TLS13" && ! "$using_sockets"; then - outln - prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\""; + prln_local_problem "$OPENSSL doesn't support \"s_client -$proto\""; return 0 fi - if { [[ $proto != tls1_3 ]] || "$HAS_TLS13"; } && { [[ $proto != ssl3 ]] || "$HAS_SSL3"; }; then + if sclient_supported "-$proto"; then if [[ $proto == tls1_2 ]] && "$SERVER_SIZE_LIMIT_BUG" && \ [[ "$(count_ciphers "$(actually_supported_osslciphers "ALL:COMPLEMENTOFALL" "" "")")" -gt 127 ]]; then order="$(check_tls12_pref "$wide")" @@ -8157,7 +8188,7 @@ get_server_certificate() { for proto in $protocols_to_try; do [[ 1 -eq $(has_server_protocol $proto) ]] && continue - [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && continue + sclient_supported "-$proto" || continue addcmd="" $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $ciphers_to_test -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug $npn_params -status -msg") $ERRFILE >$TMPFILE if sclient_connect_successful $? $TMPFILE; then @@ -8167,7 +8198,7 @@ get_server_certificate() { done # this loop is needed for IIS6 and others which have a handshake size limitations if [[ $success -eq 7 ]]; then # "-status" above doesn't work for GOST only servers, so we do another test without it and see whether that works then: - [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && return 7 + sclient_supported "-$proto" || return 7 $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $ciphers_to_test -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug") >$ERRFILE >$TMPFILE if ! sclient_connect_successful $? $TMPFILE; then if [ -z "$1" ]; then @@ -17396,8 +17427,8 @@ run_sweet32() { fi for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do [[ $nr_supported_ciphers -eq 0 ]] && break - ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue if [[ "$proto" != -no_ssl2 ]]; then + sclient_supported "$proto" || continue "$FAST" && break [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue fi @@ -17553,7 +17584,7 @@ run_tls_poodle() { # the countermeasure to protect against protocol downgrade attacks. # run_tls_fallback_scsv() { - local -i ret=0 debug_level + local -i ret=0 debug_level hsp local high_proto="" low_proto="" local p high_proto_str protos_to_try local using_sockets=true @@ -17578,21 +17609,26 @@ run_tls_fallback_scsv() { return 0 fi for p in tls1_2 tls1_1 tls1 ssl3; do - [[ $(has_server_protocol "$p") -eq 1 ]] && continue - if [[ $(has_server_protocol "$p") -eq 0 ]]; then + hsp=$(has_server_protocol "$p") + [[ $hsp -eq 1 ]] && continue + if [[ $hsp -eq 0 ]]; then high_proto="$p" break fi - - if [[ "$p" == ssl3 ]] && ! "$HAS_SSL3"; then - "$using_sockets" || continue - tls_sockets "00" "$TLS_CIPHER" "" "" "true" + if ! sclient_supported "-$p"; then + "$using_sockets"|| continue + case "$p" in + "tls1_2") tls_sockets "03" "$TLS12_CIPHER" "" "" "true" ;; + "tls1_1") tls_sockets "02" "$TLS_CIPHER" "" "" "true" ;; + "tls1") tls_sockets "01" "$TLS_CIPHER" "" "" "true" ;; + "ssl3") tls_sockets "00" "$TLS_CIPHER" "" "" "true" ;; + esac if [[ $? -eq 0 ]]; then high_proto="$p" - add_proto_offered ssl3 yes + add_proto_offered "$p" yes break else - add_proto_offered ssl3 no + add_proto_offered "$p" no fi else $OPENSSL s_client $(s_client_options "-$p $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE >$ERRFILE >$TMPFILE $TMPFILE 2>>$ERRFILE &1 | grep -aiq "unknown option" || HAS_SSL2=true $OPENSSL s_client -ssl3 &1 | grep -aiq "unknown option" || HAS_SSL3=true + $OPENSSL s_client -tls1 &1 | grep -aiq "unknown option" || HAS_TLS1=true + $OPENSSL s_client -tls1_1 &1 | grep -aiq "unknown option" || HAS_TLS11=true + $OPENSSL s_client -tls1_2 &1 | grep -aiq "unknown option" || HAS_TLS12=true $OPENSSL s_client -tls1_3 &1 | grep -aiq "unknown option" || HAS_TLS13=true $OPENSSL s_client -no_ssl2 &1 | grep -aiq "unknown option" || HAS_NO_SSL2=true @@ -20478,6 +20552,9 @@ OSSL_SUPPORTED_CURVES: $OSSL_SUPPORTED_CURVES HAS_IPv6: $HAS_IPv6 HAS_SSL2: $HAS_SSL2 HAS_SSL3: $HAS_SSL3 +HAS_TLS1: $HAS_TLS1 +HAS_TLS11: $HAS_TLS11 +HAS_TLS12: $HAS_TLS12 HAS_TLS13: $HAS_TLS13 HAS_X448: $HAS_X448 HAS_X25519: $HAS_X25519 @@ -21707,12 +21784,7 @@ determine_optimal_proto() { if [[ -n "$1" ]]; then # STARTTLS workaround needed see https://github.com/drwetter/testssl.sh/issues/188 -- kind of odd for STARTTLS_OPTIMAL_PROTO in -tls1_2 -tls1 -ssl3 -tls1_1 -tls1_3 -ssl2; do - case $STARTTLS_OPTIMAL_PROTO in - -tls1_3) "$HAS_TLS13" || continue ;; - -ssl3) "$HAS_SSL3" || continue ;; - -ssl2) "$HAS_SSL2" || continue ;; - *) ;; - esac + sclient_supported "$STARTTLS_OPTIMAL_PROTO" || continue $OPENSSL s_client $(s_client_options "$STARTTLS_OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" $PROXY -msg $STARTTLS $SNI") $TMPFILE 2>>$ERRFILE if sclient_auth $? $TMPFILE; then all_failed=false @@ -21726,12 +21798,7 @@ determine_optimal_proto() { else # No STARTTLS for proto in '' -tls1_2 -tls1 -tls1_3 -ssl3 -tls1_1 -ssl2; do - case $proto in - -tls1_3) "$HAS_TLS13" || continue ;; - -ssl3) "$HAS_SSL3" || continue ;; - -ssl2) "$HAS_SSL2" || continue ;; - *) ;; - esac + [[ -z "$proto" ]] || sclient_supported "$proto" || continue # Only send $GET_REQ11 in case of a non-empty $URL_PATH, as it # is not needed otherwise. Also, sending $GET_REQ11 may cause # problems if the server being tested is not an HTTPS server,