From 2a13643bb1e20983f621d732b19cf20e5b98c2b9 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 12 Feb 2019 12:43:57 -0500 Subject: [PATCH] Reorganize cipher_pref_check() This PR reorganizes cipher_pref_check(). Currently, cipher_pref_check() runs a for loop, which loops over each protocol and prints the set of supported ciphers for each protocol. This PR simply places the body of the for loop in a separate function from the loop itself. This allows cipher_pref_check() to be called for just a single protocol rather than for all protocols. Another PR will make a similar change to run_cipher_per_proto(). The reason for this change is that cipher_pref_check() was only intended to be used in cases in which the server enforces a cipher preference order. Some servers, however, enforce an order for some protocols, but not for others. The change in this PR will make it possible in the future to call cipher_pref_check() only for protocols in which the server enforces a cipher order. --- testssl.sh | 379 +++++++++++++++++++++++++++-------------------------- 1 file changed, 190 insertions(+), 189 deletions(-) diff --git a/testssl.sh b/testssl.sh index c8ab76a..dbb7548 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6106,7 +6106,17 @@ run_server_preference() { fi if "$has_cipher_order"; then - cipher_pref_check + "$FAST" && using_sockets=false + [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false + + pr_bold " Cipher order" + + while read proto_ossl proto_hex proto_txt; do + cipher_pref_check "$proto_ossl" "$proto_hex" "$proto_txt" "$using_sockets" + done <<< "$(tm_out " ssl3 00 SSLv3\n tls1 01 TLSv1\n tls1_1 02 TLSv1.1\n tls1_2 03 TLSv1.2\n tls1_3 04 TLSv1.3\n")" + outln + + outln else pr_bold " Negotiated cipher per proto"; outln " $limitedsense" i=1 @@ -6295,220 +6305,211 @@ check_tls12_pref() { cipher_pref_check() { - local p proto proto_hex - local tested_cipher cipher order rfc_ciph rfc_order + local p="$1" proto_hex="$2" proto="$3" + local using_sockets="$4" + local tested_cipher cipher order rfc_cipher rfc_order local overflow_probe_cipherlist="ALL:-ECDHE-RSA-AES256-GCM-SHA384:-AES128-SHA:-DES-CBC3-SHA" local -i i nr_ciphers nr_nonossl_ciphers num_bundles mod_check bundle_size bundle end_of_bundle success local hexc ciphers_to_test local -a rfc_ciph hexcode ciphers_found ciphers_found2 local -a -i index - local using_sockets=true ciphers_found_with_sockets + local ciphers_found_with_sockets - "$SSL_NATIVE" && using_sockets=false - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false + order=""; ciphers_found_with_sockets=false + if [[ $p == ssl3 ]] && ! "$HAS_SSL3" && ! "$using_sockets"; then + out "\n SSLv3: "; pr_local_problem "$OPENSSL doesn't support \"s_client -ssl3\""; + return 0 + fi + if [[ $p == tls1_3 ]] && ! "$HAS_TLS13" && ! "$using_sockets"; then + out "\n TLSv1.3 "; pr_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\""; + return 0 + fi - pr_bold " Cipher order" + [[ $(has_server_protocol "$p") -eq 1 ]] && return 0 - while read p proto_hex proto; do - order=""; ciphers_found_with_sockets=false - if [[ $p == ssl3 ]] && ! "$HAS_SSL3" && ! "$using_sockets"; then - out "\n SSLv3: "; pr_local_problem "$OPENSSL doesn't support \"s_client -ssl3\""; - continue - fi - if [[ $p == tls1_3 ]] && ! "$HAS_TLS13" && ! "$using_sockets"; then - out "\n TLSv1.3 "; pr_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\""; - continue - fi + if ( [[ $p != tls1_3 ]] || "$HAS_TLS13" ) && ( [[ $p != ssl3 ]] || "$HAS_SSL3" ); then + # with the supplied binaries SNI works also for SSLv3 - [[ $(has_server_protocol "$p") -eq 1 ]] && continue - - if ( [[ $p != tls1_3 ]] || "$HAS_TLS13" ) && ( [[ $p != ssl3 ]] || "$HAS_SSL3" ); then - # with the supplied binaries SNI works also for SSLv3 - - if [[ $p == tls1_2 ]] && ! "$SERVER_SIZE_LIMIT_BUG"; then - # for some servers the ClientHello is limited to 128 ciphers or the ClientHello itself has a length restriction. - # So far, this was only observed in TLS 1.2, affected are e.g. old Cisco LBs or ASAs, see issue #189 - # To check whether a workaround is needed we send a large list of ciphers/big client hello. If connect fails, - # we hit the bug and automagically do the workaround. Cost: this is for all servers only 1x more connect - $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "$overflow_probe_cipherlist" -connect $NODEIP:$PORT $PROXY $SNI") >$ERRFILE >$TMPFILE - if ! sclient_connect_successful $? $TMPFILE; then + if [[ $p == tls1_2 ]] && ! "$SERVER_SIZE_LIMIT_BUG"; then + # for some servers the ClientHello is limited to 128 ciphers or the ClientHello itself has a length restriction. + # So far, this was only observed in TLS 1.2, affected are e.g. old Cisco LBs or ASAs, see issue #189 + # To check whether a workaround is needed we send a large list of ciphers/big client hello. If connect fails, + # we hit the bug and automagically do the workaround. Cost: this is for all servers only 1x more connect + $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "$overflow_probe_cipherlist" -connect $NODEIP:$PORT $PROXY $SNI") >$ERRFILE >$TMPFILE + if ! sclient_connect_successful $? $TMPFILE; then #FIXME this needs to be handled differently. We need 2 status: BUG={true,false,not tested yet} - SERVER_SIZE_LIMIT_BUG=true - fi - fi - if [[ $p == tls1_2 ]] && "$SERVER_SIZE_LIMIT_BUG"; then - order="$(check_tls12_pref)" - else - tested_cipher="" - while true; do - if [[ $p != tls1_3 ]]; then - ciphers_to_test="-cipher ALL:COMPLEMENTOFALL$tested_cipher" - else - ciphers_to_test="" - for cipher in $(colon_to_spaces "$TLS13_OSSL_CIPHERS"); do - [[ ! "$tested_cipher" =~ ":-"$cipher ]] && ciphers_to_test+=":$cipher" - done - [[ -z "$ciphers_to_test" ]] && break - ciphers_to_test="-ciphersuites ${ciphers_to_test:1}" - fi - $OPENSSL s_client $(s_client_options "$STARTTLS -"$p" $BUGS $ciphers_to_test -connect $NODEIP:$PORT $PROXY $SNI") >$ERRFILE >$TMPFILE - sclient_connect_successful $? $TMPFILE || break - cipher=$(get_cipher $TMPFILE) - [[ -z "$cipher" ]] && break - order+="$cipher " - tested_cipher+=":-"$cipher - "$FAST" && break - done + SERVER_SIZE_LIMIT_BUG=true fi fi - - nr_nonossl_ciphers=0 - if "$using_sockets"; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - ciphers_found[i]=false - hexc="${TLS_CIPHER_HEXCODE[i]}" - if [[ ${#hexc} -eq 9 ]]; then - if [[ " $order " =~ " ${TLS_CIPHER_OSSL_NAME[i]} " ]]; then - ciphers_found[i]=true - else - ciphers_found2[nr_nonossl_ciphers]=false - hexcode[nr_nonossl_ciphers]="${hexc:2:2},${hexc:7:2}" - rfc_ciph[nr_nonossl_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - index[nr_nonossl_ciphers]=$i - # Only test ciphers that are relevant to the protocol. - if [[ "$p" == tls1_3 ]]; then - [[ "${hexc:2:2}" == "13" ]] && nr_nonossl_ciphers+=1 - elif [[ "$p" == tls1_2 ]]; then - [[ "${hexc:2:2}" != 13 ]] && nr_nonossl_ciphers+=1 - elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \ - [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then - nr_nonossl_ciphers+=1 - fi - fi - fi - done - fi - - if [[ $nr_nonossl_ciphers -eq 0 ]]; then - num_bundles=0 - elif [[ $p != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then - num_bundles=1 - bundle_size=$nr_nonossl_ciphers + if [[ $p == tls1_2 ]] && "$SERVER_SIZE_LIMIT_BUG"; then + order="$(check_tls12_pref)" else - num_bundles=$nr_nonossl_ciphers/128 - mod_check=$nr_nonossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_nonossl_ciphers/$num_bundles - mod_check=$nr_nonossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers + tested_cipher="" while true; do - ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - [[ $? -ne 0 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${rfc_ciph[i]}" ]] && ciphers_found2[i]=true && break - done - i=${index[i]} - ciphers_found[i]=true - ciphers_found_with_sockets=true - if [[ $p != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then - # Throw out the results found so far and start over using just sockets - bundle=$num_bundles - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - ciphers_found[i]=true + if [[ $p != tls1_3 ]]; then + ciphers_to_test="-cipher ALL:COMPLEMENTOFALL$tested_cipher" + else + ciphers_to_test="" + for cipher in $(colon_to_spaces "$TLS13_OSSL_CIPHERS"); do + [[ ! "$tested_cipher" =~ ":-"$cipher ]] && ciphers_to_test+=":$cipher" done - break + [[ -z "$ciphers_to_test" ]] && break + ciphers_to_test="-ciphersuites ${ciphers_to_test:1}" fi + $OPENSSL s_client $(s_client_options "$STARTTLS -"$p" $BUGS $ciphers_to_test -connect $NODEIP:$PORT $PROXY $SNI") >$ERRFILE >$TMPFILE + sclient_connect_successful $? $TMPFILE || break + cipher=$(get_cipher $TMPFILE) + [[ -z "$cipher" ]] && break + order+="$cipher " + tested_cipher+=":-"$cipher + "$FAST" && break done - done + fi + fi - # If additional ciphers were found using sockets and there is no - # SERVER_SIZE_LIMIT_BUG, then just use sockets to find the cipher order. - # If there is a SERVER_SIZE_LIMIT_BUG, then use sockets to find the cipher - # order, but starting with the list of ciphers supported by the server. - if "$ciphers_found_with_sockets"; then - order="" - nr_ciphers=0 - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc="${TLS_CIPHER_HEXCODE[i]}" - if "${ciphers_found[i]}" && [[ ${#hexc} -eq 9 ]]; then - ciphers_found2[nr_ciphers]=false - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - if [[ "$p" == "tls1_3" ]]; then - [[ "${hexc:2:2}" == "13" ]] && nr_ciphers+=1 - elif [[ "$p" == "tls1_2" ]]; then - [[ "${hexc:2:2}" != "13" ]] && nr_ciphers+=1 + nr_nonossl_ciphers=0 + if "$using_sockets"; then + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + ciphers_found[i]=false + hexc="${TLS_CIPHER_HEXCODE[i]}" + if [[ ${#hexc} -eq 9 ]]; then + if [[ " $order " =~ " ${TLS_CIPHER_OSSL_NAME[i]} " ]]; then + ciphers_found[i]=true + else + ciphers_found2[nr_nonossl_ciphers]=false + hexcode[nr_nonossl_ciphers]="${hexc:2:2},${hexc:7:2}" + rfc_ciph[nr_nonossl_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" + index[nr_nonossl_ciphers]=$i + # Only test ciphers that are relevant to the protocol. + if [[ "$p" == tls1_3 ]]; then + [[ "${hexc:2:2}" == "13" ]] && nr_nonossl_ciphers+=1 + elif [[ "$p" == tls1_2 ]]; then + [[ "${hexc:2:2}" != 13 ]] && nr_nonossl_ciphers+=1 elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \ [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \ [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \ [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then - nr_ciphers+=1 + nr_nonossl_ciphers+=1 fi fi - done - while true; do - ciphers_to_test="" - for (( i=0; i < nr_ciphers; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - [[ $? -ne 0 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=0; i < nr_ciphers; i++ )); do - [[ "$cipher" == ${rfc_ciph[i]} ]] && ciphers_found2[i]=true && break - done - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ $TLS_NR_CIPHERS -ne 0 ]]; then - cipher="$(rfc2openssl "$cipher")" - # If there is no OpenSSL name for the cipher, then use the RFC name - [[ -z "$cipher" ]] && cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - fi - order+="$cipher " - done - elif [[ -n "$order" ]] && [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then - rfc_order="" - while read -d " " cipher; do - rfc_ciph="$(openssl2rfc "$cipher")" - if [[ -n "$rfc_ciph" ]]; then - rfc_order+="$rfc_ciph " - else - rfc_order+="$cipher " - fi - done <<< "$order" - order="$rfc_order" - fi - - if [[ -n "$order" ]]; then - add_tls_offered "$p" yes - outln - out "$(printf " %-10s " "$proto: ")" - if [[ "$COLOR" -le 2 ]]; then - out "$(out_row_aligned_max_width "$order" " " $TERM_WIDTH)" - else - out_row_aligned_max_width_by_entry "$order" " " $TERM_WIDTH pr_cipher_quality fi - fileout "cipherorder_${proto//./_}" "INFO" "$order" - fi - done <<< "$(tm_out " ssl3 00 SSLv3\n tls1 01 TLSv1\n tls1_1 02 TLSv1.1\n tls1_2 03 TLSv1.2\n tls1_3 04 TLSv1.3\n")" - outln + done + fi - outln - tmpfile_handle ${FUNCNAME[0]}.txt + if [[ $nr_nonossl_ciphers -eq 0 ]]; then + num_bundles=0 + elif [[ $p != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then + num_bundles=1 + bundle_size=$nr_nonossl_ciphers + else + num_bundles=$nr_nonossl_ciphers/128 + mod_check=$nr_nonossl_ciphers%128 + [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 + + bundle_size=$nr_nonossl_ciphers/$num_bundles + mod_check=$nr_nonossl_ciphers%$num_bundles + [[ $mod_check -ne 0 ]] && bundle_size+=1 + fi + + for (( bundle=0; bundle < num_bundles; bundle++ )); do + end_of_bundle=$bundle*$bundle_size+$bundle_size + [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers + while true; do + ciphers_to_test="" + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" + done + [[ -z "$ciphers_to_test" ]] && break + tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" + [[ $? -ne 0 ]] && break + cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + [[ "$cipher" == "${rfc_ciph[i]}" ]] && ciphers_found2[i]=true && break + done + i=${index[i]} + ciphers_found[i]=true + ciphers_found_with_sockets=true + if [[ $p != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then + # Throw out the results found so far and start over using just sockets + bundle=$num_bundles + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + ciphers_found[i]=true + done + break + fi + done + done + + # If additional ciphers were found using sockets and there is no + # SERVER_SIZE_LIMIT_BUG, then just use sockets to find the cipher order. + # If there is a SERVER_SIZE_LIMIT_BUG, then use sockets to find the cipher + # order, but starting with the list of ciphers supported by the server. + if "$ciphers_found_with_sockets"; then + order="" + nr_ciphers=0 + for (( i=0; i < TLS_NR_CIPHERS; i++ )); do + hexc="${TLS_CIPHER_HEXCODE[i]}" + if "${ciphers_found[i]}" && [[ ${#hexc} -eq 9 ]]; then + ciphers_found2[nr_ciphers]=false + hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" + rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" + if [[ "$p" == "tls1_3" ]]; then + [[ "${hexc:2:2}" == "13" ]] && nr_ciphers+=1 + elif [[ "$p" == "tls1_2" ]]; then + [[ "${hexc:2:2}" != "13" ]] && nr_ciphers+=1 + elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \ + [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \ + [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \ + [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then + nr_ciphers+=1 + fi + fi + done + while true; do + ciphers_to_test="" + for (( i=0; i < nr_ciphers; i++ )); do + ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" + done + [[ -z "$ciphers_to_test" ]] && break + tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" + [[ $? -ne 0 ]] && break + cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") + for (( i=0; i < nr_ciphers; i++ )); do + [[ "$cipher" == ${rfc_ciph[i]} ]] && ciphers_found2[i]=true && break + done + if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ $TLS_NR_CIPHERS -ne 0 ]]; then + cipher="$(rfc2openssl "$cipher")" + # If there is no OpenSSL name for the cipher, then use the RFC name + [[ -z "$cipher" ]] && cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") + fi + order+="$cipher " + done + elif [[ -n "$order" ]] && [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then + rfc_order="" + while read -d " " cipher; do + rfc_cipher="$(openssl2rfc "$cipher")" + if [[ -n "$rfc_cipher" ]]; then + rfc_order+="$rfc_cipher " + else + rfc_order+="$cipher " + fi + done <<< "$order" + order="$rfc_order" + fi + + if [[ -n "$order" ]]; then + add_tls_offered "$p" yes + outln + out "$(printf " %-10s " "$proto: ")" + if [[ "$COLOR" -le 2 ]]; then + out "$(out_row_aligned_max_width "$order" " " $TERM_WIDTH)" + else + out_row_aligned_max_width_by_entry "$order" " " $TERM_WIDTH pr_cipher_quality + fi + fileout "cipherorder_${proto//./_}" "INFO" "$order" + fi + + tmpfile_handle ${FUNCNAME[0]}-$p.txt return 0 }