From ea10f1ee93dd3dff45f3a6ea9ce25f608a42232d Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 15 Nov 2016 14:45:50 -0500 Subject: [PATCH 1/2] Faster version of run_allciphers() This PR speeds up the implementation of `run_allciphers()` by introducing a number of changes: * Rather than check for implemented ciphers in a hierarchical manner (as introduced in #326), this PR follows the approach of `cipher_pref_check()`. Testing a block of ciphers, marking the selected cipher as implemented, and then testing same block of ciphers, minus those that have previously been selected, until a test fails. Thus the number of calls to `$OPENSSL s_client` is just one more than the number of ciphers implemented. (Since some servers cannot handle ClientHellos with more than 128 messages, the tests are performed on blocks of 128 or few ciphers. So, if OpenSSL supports 197 ciphers, the number of calls to `$OPENSSL s_client` is 2 plus the number of ciphers supported by the server. * If $using_sockets is true, then OpenSSL is used first to find all supported ciphers that OpenSSL supports (since OpenSSL is faster than `tls_sockets()`), and then `tls_sockets()` is only used to test those cipher suites that were not found to be supported by OpenSSL. * The `prepare_debug()` function, which reads in `$CIPHERS_BY_STRENGTH_FILE` determines which ciphers are supported by the version of OpenSSL being used. If a version of OpenSSL older than 1.0 is being used, then this is used to determine which ciphers to test using OpenSSL rather than using `$OPENSSL ciphers -V`. Following the approach of `cipher_pref_check()` reduces the number of queries to the server. Using OpenSSL before `tls_sockets()` reduces the number of calls to `tls_sockets()` to 3 plus the number of ciphers supported by the server that are not supported by OpenSSL, so the cost penalty over just using OpenSSL is fairly small. --- testssl.sh | 338 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 206 insertions(+), 132 deletions(-) diff --git a/testssl.sh b/testssl.sh index d0ddbad..2ed8372 100755 --- a/testssl.sh +++ b/testssl.sh @@ -354,6 +354,7 @@ declare TLS_CIPHER_KX=() declare TLS_CIPHER_AUTH=() declare TLS_CIPHER_ENC=() declare TLS_CIPHER_EXPORT=() +declare TLS_CIPHER_OSSL_SUPPORTED=() ###### output functions ###### # a little bit of sanitzing with bash internal search&replace -- otherwise printf will hiccup at '%' and '--' does the rest. @@ -2026,31 +2027,34 @@ test_just_one(){ # test for all ciphers locally configured (w/o distinguishing whether they are good or bad) run_allciphers() { - local tmpfile - local -i nr_ciphers=0 ret - local n auth mac export hexc sslv2_ciphers="" - local -a normalized_hexcode hexcode ciph sslvers kx enc export2 - local -i i j parent child end_of_bundle round_num bundle_size num_bundles mod_check - local -a ciphers_found - local dhlen - local available - local ciphers_to_test - local sslv2_supported=false + local -i nr_ciphers_tested=0 nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 ret + local n auth mac export hexc sslv2_ciphers="" s + local -a normalized_hexcode hexcode ciph sslvers kx enc export2 sigalg ossl_supported + local -i i end_of_bundle bundle bundle_size num_bundles mod_check + local -a ciphers_found ciphers_found2 hexcode2 ciph2 sslvers2 rfc_ciph2 + local -i -a index + local dhlen available ciphers_to_test supported_sslv2_ciphers addcmd="" local has_dh_bits="$HAS_DH_BITS" local using_sockets=true "$SSL_NATIVE" && using_sockets=false "$FAST" && using_sockets=false - if "$using_sockets"; then - # get a list of all the cipher suites to test (only need the hexcode, ciph, kx, enc, and export values) + # get a list of all the cipher suites to test + if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc=$(echo "${TLS_CIPHER_HEXCODE[i]}" | tr 'A-Z' 'a-z') + hexc="$(tolower "${TLS_CIPHER_HEXCODE[i]}")" ciph[i]="${TLS_CIPHER_OSSL_NAME[i]}" sslvers[i]="${TLS_CIPHER_SSLVERS[i]}" kx[i]="${TLS_CIPHER_KX[i]}" enc[i]="${TLS_CIPHER_ENC[i]}" export2[i]="${TLS_CIPHER_EXPORT[i]}" + ciphers_found[i]=false + sigalg[i]="" + ossl_supported[i]=${TLS_CIPHER_OSSL_SUPPORTED[i]} + if "$using_sockets" && ! "$HAS_DH_BITS" && ( [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]] ); then + ossl_supported[i]=false + fi if [[ ${#hexc} -eq 9 ]]; then hexcode[i]="${hexc:2:2},${hexc:7:2}" if [[ "${hexc:2:2}" == "00" ]]; then @@ -2063,157 +2067,207 @@ run_allciphers() { normalized_hexcode[i]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}" sslv2_ciphers="$sslv2_ciphers, ${hexcode[i]}" fi + if "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}"; then + nr_ciphers_tested+=1 + fi done nr_ciphers=$TLS_NR_CIPHERS - sslv2_sockets "${sslv2_ciphers:2}" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - sslv2_supported=true - fi else - # get a list of all the cipher suites to test (only need the hexcode, ciph, sslvers, kx, enc, and export values) - while read hexcode[nr_ciphers] n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do + while read hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do + ciphers_found[nr_ciphers]=false + if [[ ${#hexc} -eq 9 ]]; then + if [[ "${hexc:2:2}" == "00" ]]; then + normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:7:2}")" + else + normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}")" + fi + else + normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}${hexc:12:2}")" + fi + sigalg[nr_ciphers]="" + ossl_supported[nr_ciphers]=true nr_ciphers=$nr_ciphers+1 done < <($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>>$ERRFILE) + nr_ciphers_tested=$nr_ciphers + fi - if "$HAS_SSL2"; then - $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE = 128 ciphers. - for (( round_num=0; bundle_size/4 >= 128; bundle_size/=4 )); do - round_num=$round_num+1 - for (( i=4**$round_num; i<2*4**$round_num; i++ )); do - ciphers_found[i]=true + if [[ $nr_ossl_ciphers -eq 0 ]]; then + num_bundles=0 + else + # Some servers can't handle a handshake with >= 128 ciphers. So, + # test cipher suites in bundles of 128 or less. + num_bundles=$nr_ossl_ciphers/128 + mod_check=$nr_ossl_ciphers%128 + [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 + + bundle_size=$nr_ossl_ciphers/$num_bundles + mod_check=$nr_ossl_ciphers%$num_bundles + [[ $mod_check -ne 0 ]] && bundle_size+=1 + fi + + "$HAS_NO_SSL2" && addcmd="-no_ssl2" + for (( bundle=0; bundle < num_bundles; bundle++ )); do + end_of_bundle=$bundle*$bundle_size+$bundle_size + [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers + for (( success=0; success==0 ; 1 )); do + ciphers_to_test="" + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + [[ "${sslvers2[i]}" != "SSLv2" ]] && ! "${ciphers_found2[i]}" && ciphers_to_test+=":${ciph2[i]}" + done + success=1 + if [[ -n "$ciphers_to_test" ]]; then + $OPENSSL s_client $addcmd -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE =1; bundle_size/=4 )); do - # Note that since the number of ciphers isn't a power of 4, the number - # of bundles may be may be less than 4**(round_num+1), and the final - # bundle may have fewer than bundle_size ciphers. - num_bundles=$nr_ciphers/$bundle_size - mod_check=$nr_ciphers%$bundle_size - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - for (( i=0; i$TMPFILE 2>$ERRFILE $TMPFILE - fi + if [[ $nr_nonossl_ciphers -eq 0 ]]; then + num_bundles=0 + else + # Some servers can't handle a handshake with >= 128 ciphers. So, + # test cipher suites in bundles of 128 or less. + 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 + for (( success=0; success==0 ; 1 )); do + ciphers_to_test="" + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + [[ "${sslvers2[i]}" != "SSLv2" ]] && ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}" + done + success=1 + if [[ -n "$ciphers_to_test" ]]; then + if "$SHOW_SIGALGO"; then + tls_sockets "03" "${ciphers_to_test:2}, 00,ff" "all" else - $OPENSSL s_client -cipher "${ciph[i]}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE $TEMPDIR/environment.txt << EOF @@ -9080,8 +9135,8 @@ EOF 0x00,0x26 - EXP-KRB5-DES-CBC-SHA TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA SSLv3 Kx=KRB5 Au=KRB5 Enc=DES(40) Mac=SHA1 export 0x00,0x2A - EXP-KRB5-RC2-CBC-MD5 TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 SSLv3 Kx=KRB5 Au=KRB5 Enc=RC2(40) Mac=MD5 export 0x00,0x29 - EXP-KRB5-DES-CBC-MD5 TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 SSLv3 Kx=KRB5 Au=KRB5 Enc=DES(40) Mac=MD5 export - 0x00,0x0B - - TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/DSS Au=DH Enc=DES(40) Mac=SHA1 export - 0x00,0x0E - - TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/RSA Au=DH Enc=DES(40) Mac=SHA1 export + 0x00,0x0B - EXP-DH-DSS-DES-CBC-SHA TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/DSS Au=DH Enc=DES(40) Mac=SHA1 export + 0x00,0x0E - EXP-DH-RSA-DES-CBC-SHA TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/RSA Au=DH Enc=DES(40) Mac=SHA1 export 0x00,0x17 - EXP-ADH-RC4-MD5 TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 SSLv3 Kx=DH(512) Au=None Enc=RC4(40) Mac=MD5 export 0x00,0x03 - EXP-RC4-MD5 TLS_RSA_EXPORT_WITH_RC4_40_MD5 SSLv3 Kx=RSA(512) Au=RSA Enc=RC4(40) Mac=MD5 export 0x02,0x00,0x80 - EXP-RC4-MD5 SSL_CK_RC4_128_EXPORT40_WITH_MD5 SSLv2 Kx=RSA(512) Au=RSA Enc=RC4(40) Mac=MD5 export @@ -9112,7 +9167,26 @@ EOF 0xFF,0x87 - GOST2012256-NULL-STREEBOG256 - SSLv3 Kx=GOST Au=GOST01 Enc=None Mac=STREEBOG256 EOF - while read TLS_CIPHER_HEXCODE[TLS_NR_CIPHERS] n TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS] TLS_CIPHER_RFC_NAME[TLS_NR_CIPHERS] TLS_CIPHER_SSLVERS[TLS_NR_CIPHERS] TLS_CIPHER_KX[TLS_NR_CIPHERS] TLS_CIPHER_AUTH[TLS_NR_CIPHERS] TLS_CIPHER_ENC[TLS_NR_CIPHERS] TLS_CIPHER_EXPORT[TLS_NR_CIPHERS]; do + "$HAS_SSL2" && ossl_supported_sslv2="$($OPENSSL ciphers -ssl2 -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE)" + ossl_supported_tls="$($OPENSSL ciphers -tls1 -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE)" + while read hexc n TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS] TLS_CIPHER_RFC_NAME[TLS_NR_CIPHERS] TLS_CIPHER_SSLVERS[TLS_NR_CIPHERS] TLS_CIPHER_KX[TLS_NR_CIPHERS] TLS_CIPHER_AUTH[TLS_NR_CIPHERS] TLS_CIPHER_ENC[TLS_NR_CIPHERS] TLS_CIPHER_EXPORT[TLS_NR_CIPHERS]; do + TLS_CIPHER_HEXCODE[TLS_NR_CIPHERS]="$hexc" + TLS_CIPHER_OSSL_SUPPORTED[TLS_NR_CIPHERS]=false + if [[ ${#hexc} -eq 9 ]]; then + if [[ $OSSL_VER_MAJOR -lt 1 ]]; then + [[ ":${ossl_supported_tls}:" =~ ":${TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS]}:" ]] && TLS_CIPHER_OSSL_SUPPORTED[TLS_NR_CIPHERS]=true + else + ossl_ciph="$(grep -w "$hexc" <<< "$ossl_supported_tls" | awk '{ print $3 }')" + if [[ -n "$ossl_ciph" ]]; then + TLS_CIPHER_OSSL_SUPPORTED[TLS_NR_CIPHERS]=true + [[ "$ossl_ciph" != "${TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS]}" ]] && TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS]="$ossl_ciph" + fi + fi + elif [[ $OSSL_VER_MAJOR -lt 1 ]]; then + [[ ":${ossl_supported_sslv2}:" =~ ":${TLS_CIPHER_OSSL_NAME[TLS_NR_CIPHERS]}:" ]] && TLS_CIPHER_OSSL_SUPPORTED[TLS_NR_CIPHERS]=true + else + grep -qw "$hexc" <<< "$ossl_supported_sslv2" && TLS_CIPHER_OSSL_SUPPORTED[TLS_NR_CIPHERS]=true + fi TLS_NR_CIPHERS+=1 done < $CIPHERS_BY_STRENGTH_FILE } From b0ac21fe319059983d79a2635d077cd52cdaeae2 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 15 Nov 2016 15:13:09 -0500 Subject: [PATCH 2/2] Add two missing OpenSSL cipher names --- etc/cipher-mapping.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/cipher-mapping.txt b/etc/cipher-mapping.txt index 96e88ab..7315d54 100644 --- a/etc/cipher-mapping.txt +++ b/etc/cipher-mapping.txt @@ -327,8 +327,8 @@ 0x00,0x26 - EXP-KRB5-DES-CBC-SHA TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA SSLv3 Kx=KRB5 Au=KRB5 Enc=DES(40) Mac=SHA1 export 0x00,0x2A - EXP-KRB5-RC2-CBC-MD5 TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 SSLv3 Kx=KRB5 Au=KRB5 Enc=RC2(40) Mac=MD5 export 0x00,0x29 - EXP-KRB5-DES-CBC-MD5 TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 SSLv3 Kx=KRB5 Au=KRB5 Enc=DES(40) Mac=MD5 export - 0x00,0x0B - - TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/DSS Au=DH Enc=DES(40) Mac=SHA1 export - 0x00,0x0E - - TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/RSA Au=DH Enc=DES(40) Mac=SHA1 export + 0x00,0x0B - EXP-DH-DSS-DES-CBC-SHA TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/DSS Au=DH Enc=DES(40) Mac=SHA1 export + 0x00,0x0E - EXP-DH-RSA-DES-CBC-SHA TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/RSA Au=DH Enc=DES(40) Mac=SHA1 export 0x00,0x17 - EXP-ADH-RC4-MD5 TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 SSLv3 Kx=DH(512) Au=None Enc=RC4(40) Mac=MD5 export 0x00,0x03 - EXP-RC4-MD5 TLS_RSA_EXPORT_WITH_RC4_40_MD5 SSLv3 Kx=RSA(512) Au=RSA Enc=RC4(40) Mac=MD5 export 0x02,0x00,0x80 - EXP-RC4-MD5 SSL_CK_RC4_128_EXPORT40_WITH_MD5 SSLv2 Kx=RSA(512) Au=RSA Enc=RC4(40) Mac=MD5 export