From 97652e64e0b5f8e23364890564abd49e8a527d55 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Thu, 8 Dec 2016 12:36:45 -0500 Subject: [PATCH 1/3] run_pfs() speedup + sockets This PR implements `run_pfs()` in a manner similar to `run_allciphers()`. It uses OpenSSL followed by `tls_sockets()` to test for both supported PFS cipher suites as well as elliptic curves offered. I made an attempt at addressing #548 by using different colors to print the different curve names, depending on strength. The colors chosen are exactly the same as those that would be chosen by `read_dhbits_from_file()`: ``` # bits <= 163: pr_svrty_medium 163 < # bits <= 193: pr_svrty_minor 193 < # bits <= 224: out # bits > 224: pr_done_good ``` I also added code for #464 to create a list of the DH groups from RFC 7919 that a server supports. However, since no servers seem to support this at the moment (except with TLS 1.3), I marked this code to only run if the $EXPERIMENTAL flag is set. --- testssl.sh | 375 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 279 insertions(+), 96 deletions(-) diff --git a/testssl.sh b/testssl.sh index 883c267..a19b18a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -5781,38 +5781,98 @@ run_server_defaults() { run_pfs() { local -i sclient_success - local pfs_offered=false ecdhe_offered=false - local tmpfile - local dhlen - local hexcode dash pfs_cipher sslvers kx auth enc mac curve - local pfs_cipher_list="$ROBUST_PFS_CIPHERS" - local ecdhe_cipher_list="" + local pfs_offered=false ecdhe_offered=false ffdhe_offered=false + local hexc dash pfs_cipher sslvers auth mac export curve dhlen + local -a hexcode normalized_hexcode ciph rfc_ciph kx enc ciphers_found sigalg ossl_supported + local pfs_cipher_list="$ROBUST_PFS_CIPHERS" pfs_hex_cipher_list="" ciphers_to_test + local ecdhe_cipher_list="" ecdhe_cipher_list_hex="" ffdhe_cipher_list_hex="" + local curves_hex=("00,01" "00,02" "00,03" "00,04" "00,05" "00,06" "00,07" "00,08" "00,09" "00,0a" "00,0b" "00,0c" "00,0d" "00,0e" "00,0f" "00,10" "00,11" "00,12" "00,13" "00,14" "00,15" "00,16" "00,17" "00,18" "00,19" "00,1a" "00,1b" "00,1c" "00,1d" "00,1e") local -a curves_ossl=("sect163k1" "sect163r1" "sect163r2" "sect193r1" "sect193r2" "sect233k1" "sect233r1" "sect239k1" "sect283k1" "sect283r1" "sect409k1" "sect409r1" "sect571k1" "sect571r1" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "prime192v1" "secp224k1" "secp224r1" "secp256k1" "prime256v1" "secp384r1" "secp521r1" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") local -a curves_ossl_output=("K-163" "sect163r1" "B-163" "sect193r1" "sect193r2" "K-233" "B-233" "sect239k1" "K-283" "B-283" "K-409" "B-409" "K-571" "B-571" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "P-192" "secp224k1" "P-224" "secp256k1" "P-256" "P-384" "P-521" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") - local -a supported_curves=() - local -i nr_supported_ciphers=0 nr_curves=0 i j low high - local pfs_ciphers curves_offered curves_to_test temp - local curve_found curve_used + local -a ffdhe_groups_hex=("01,00" "01,01" "01,02" "01,03" "01,04") + local -a ffdhe_groups_output=("ffdhe2048" "ffdhe3072" "ffdhe4096" "ffdhe6144" "ffdhe8192") + local -a supported_curve bits + local -i nr_supported_ciphers=0 nr_curves=0 nr_ossl_curves=0 i j low high + local pfs_ciphers curves_offered="" curves_offered_text="" curves_to_test temp + local len1 len2 curve_found + local has_dh_bits="$HAS_DH_BITS" + local using_sockets=true + + "$SSL_NATIVE" && using_sockets=false + "$FAST" && using_sockets=false + [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false outln pr_headlineln " Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4 " - if ! "$HAS_DH_BITS" && "$WIDE"; then - pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)" - fi - - nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $pfs_cipher_list)) - debugme echo $nr_supported_ciphers - debugme echo $(actually_supported_ciphers $pfs_cipher_list) - if [[ "$nr_supported_ciphers" -le "$CLIENT_MIN_PFS" ]]; then + if ! "$using_sockets"; then + [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl" + if ! "$HAS_DH_BITS" && "$WIDE"; then + [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "." + pr_warning " (Your $OPENSSL cannot show DH/ECDH bits)" + fi outln - local_problem_ln "You only have $nr_supported_ciphers PFS ciphers on the client side " - fileout "pfs" "WARN" "(Perfect) Forward Secrecy tests: Skipped. You only have $nr_supported_ciphers PFS ciphers on the client site. ($CLIENT_MIN_PFS are required)" - return 1 fi - $OPENSSL s_client -cipher $pfs_cipher_list $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $ERRFILE) + fi + export="" + + if "$using_sockets"; then + tls_sockets "03" "${pfs_hex_cipher_list:2}" + sclient_success=$? + [[ $sclient_success -eq 2 ]] && sclient_success=0 + else + debugme echo $nr_supported_ciphers + debugme echo $(actually_supported_ciphers $pfs_cipher_list) + if [[ "$nr_supported_ciphers" -le "$CLIENT_MIN_PFS" ]]; then + outln + local_problem_ln "You only have $nr_supported_ciphers PFS ciphers on the client side " + fileout "pfs" "WARN" "(Perfect) Forward Secrecy tests: Skipped. You only have $nr_supported_ciphers PFS ciphers on the client site. ($CLIENT_MIN_PFS are required)" + return 1 + fi + $OPENSSL s_client -cipher $pfs_cipher_list $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $tmpfile $TMPFILE $ERRFILE) # -V doesn't work with openssl < 1.0 + done + debugme echo $pfs_offered "$WIDE" || outln - if ! "$pfs_offered"; then - pr_svrty_medium "WARN: no PFS ciphers found" - fileout "pfs_ciphers" "MEDIUM" "(Perfect) Forward Secrecy Ciphers: no PFS ciphers found" - else - fileout "pfs_ciphers" "INFO" "(Perfect) Forward Secrecy Ciphers: $pfs_ciphers" - fi + fileout "pfs_ciphers" "INFO" "(Perfect) Forward Secrecy Ciphers: $pfs_ciphers" fi + # find out what elliptic curves are supported. if "$ecdhe_offered"; then - # find out what elliptic curves are supported. - curves_offered="" for curve in "${curves_ossl[@]}"; do + ossl_supported[nr_curves]=false + supported_curve[nr_curves]=false $OPENSSL s_client -curves $curve 2>&1 | egrep -iaq "Error with command|unknown option" - [[ $? -ne 0 ]] && nr_curves+=1 && supported_curves+=("$curve") + [[ $? -ne 0 ]] && ossl_supported[nr_curves]=true && nr_ossl_curves+=1 + nr_curves+=1 done # OpenSSL limits the number of curves that can be specified in the - # "-curves" option to 28. So, the list is broken in two since there - # are currently 30 curves defined. - for i in 1 2; do - case $i in - 1) low=0; high=$nr_curves/2 ;; - 2) low=$nr_curves/2; high=$nr_curves ;; - esac - sclient_success=0 - while [[ "$sclient_success" -eq 0 ]]; do - curves_to_test="" - for (( j=low; j < high; j++ )); do - [[ ! " $curves_offered " =~ " ${supported_curves[j]} " ]] && curves_to_test+=":${supported_curves[j]}" - done - if [[ -n "$curves_to_test" ]]; then - $OPENSSL s_client -cipher "${ecdhe_cipher_list:1}" -curves "${curves_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI &>$tmpfile $TMPFILE Date: Tue, 20 Dec 2016 13:14:40 -0500 Subject: [PATCH 2/3] test_just_one() sockets This PR implements `test_just_one()` in a similar manner to `run_allciphers()` --- testssl.sh | 272 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 238 insertions(+), 34 deletions(-) diff --git a/testssl.sh b/testssl.sh index 322eadf..2586712 100755 --- a/testssl.sh +++ b/testssl.sh @@ -2188,10 +2188,20 @@ neat_list(){ } test_just_one(){ - local hexcode n ciph sslvers kx auth enc mac export - local dhlen - local sclient_success + local hexc n auth export ciphers_to_test supported_sslv2_ciphers s + local -a hexcode normalized_hexcode ciph sslvers kx enc export2 sigalg + local -a ciphers_found ciphers_found2 ciph2 rfc_ciph rfc_ciph2 ossl_supported + local -a -i index + local -i nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 + local -i num_bundles mod_check bundle_size bundle end_of_bundle + local addcmd dhlen has_dh_bits="$HAS_DH_BITS" + local -i sclient_success local re='^[0-9A-Fa-f]+$' + local using_sockets=true + + "$SSL_NATIVE" && using_sockets=false + "$FAST" && using_sockets=false + [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false pr_headline " Testing single cipher with " if [[ $1 =~ $re ]]; then @@ -2202,46 +2212,240 @@ test_just_one(){ tjolines="$tjolines word pattern \"$1\" (ignore case)\n\n" fi outln - ! "$HAS_DH_BITS" && pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)" + if ! "$using_sockets"; then + [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl" + if ! "$HAS_DH_BITS"; then + [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "." + pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)" + fi + fi outln neat_header #for arg in $(echo $@ | sed 's/,/ /g'); do for arg in ${*//, /}; do - # 1st check whether openssl has cipher or not - $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE | while read hexcode dash ciph sslvers kx auth enc mac export ; do - # FIXME: e.g. OpenSSL < 1.0 doesn't understand "-V" --> we can't do anything about it! - normalize_ciphercode $hexcode - # is argument a number? - if [[ $arg =~ $re ]]; then - neat_list $HEXC $ciph $kx $enc | grep -qai "$arg" - else - neat_list $HEXC $ciph $kx $enc | grep -qwai "$arg" - fi - if [[ $? -eq 0 ]]; then # string matches, so we can ssl to it: - if [[ "$sslvers" == "SSLv2" ]]; then - $OPENSSL s_client -ssl2 -cipher $ciph $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY 2>$ERRFILE >$TMPFILE $ERRFILE >$TMPFILE >$ERRFILE) + fi + + # Test the SSLv2 ciphers, if any. + if "$using_sockets"; then + ciphers_to_test="" + for (( i=0; i < nr_ciphers; i++ )); do + if [[ "${sslvers[i]}" == "SSLv2" ]]; then + ciphers_to_test+=", ${hexcode[i]}" + fi + done + if [[ -n "$ciphers_to_test" ]]; then + sslv2_sockets "${ciphers_to_test:2}" "true" + if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then + supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" + "$SHOW_SIGALGO" && s="$($OPENSSL x509 -noout -text -in "$HOSTCERT" | awk -F':' '/Signature Algorithm/ { print $2 }' | head -1)" + for (( i=0 ; i$TMPFILE 2>$ERRFILE = 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" || addcmd="" + 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 + while true; do + ciphers_to_test="" + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + ! "${ciphers_found2[i]}" && ciphers_to_test+=":${ciph2[i]}" + done + [[ -z "$ciphers_to_test" ]] && break + $OPENSSL s_client $addcmd -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE = 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 + while true; do + ciphers_to_test="" + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}" + done + [[ -z "$ciphers_to_test" ]] && break + if "$SHOW_SIGALGO"; then + tls_sockets "03" "${ciphers_to_test:2}, 00,ff" "all" + else + tls_sockets "03" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" + fi + sclient_success=$? + [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break + cipher=$(awk '/Cipher *:/ { print $3 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") + for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do + [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break + done + [[ $i -eq $end_of_bundle ]] && break + i=${index[i]} + ciphers_found[i]=true + if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then + dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) + kx[i]="${kx[i]} $dhlen" + fi + "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \ + sigalg[i]="$($OPENSSL x509 -noout -text -in "$HOSTCERT" | awk -F':' '/Signature Algorithm/ { print $2 }' | head -1)" + done + done + + for (( i=0; i < nr_ciphers; i++ )); do + export="${export2[i]}" + neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" + if "${ciphers_found[i]}"; then + pr_cyan " available" + fileout "cipher_${normalized_hexcode[i]}" "INFO" "$(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}") available" + else + out " not a/v" + fileout "cipher_${normalized_hexcode[i]}" "INFO" "$(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}") not a/v" + fi + outln + done + "$using_sockets" && HAS_DH_BITS="$has_dh_bits" exit done outln From c3b300c5fb20725f70b0cdbe82f9000c6eb85efc Mon Sep 17 00:00:00 2001 From: Dirk Date: Thu, 29 Dec 2016 22:02:07 +0100 Subject: [PATCH 3/3] - cleanup ignore_no_or_lame() - reorder get_install_dir in main() so that warnings are not displayed before --help - tweak missing ~/etc msg --- testssl.sh | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9c1907e..18612ca 100755 --- a/testssl.sh +++ b/testssl.sh @@ -573,7 +573,7 @@ pr_blue() { [[ "$COLOR" -eq 2 ]] && ( "$COLORBLIND" && out "\033[1;32m$1" pr_blueln() { pr_blue "$1"; outln; } pr_warning() { [[ "$COLOR" -eq 2 ]] && out "\033[0;35m$1" || pr_underline "$1"; pr_off; } # some local problem: one test cannot be done -pr_warningln() { pr_warning "$1"; outln; } # litemagenya +pr_warningln() { pr_warning "$1"; outln; } # litemagenta pr_magenta() { [[ "$COLOR" -eq 2 ]] && out "\033[1;35m$1" || pr_underline "$1"; pr_off; } # fatal error: quitting because of this! pr_magentaln() { pr_magenta "$1"; outln; } @@ -9511,10 +9511,11 @@ get_install_dir() { if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] ; then unset ADD_RFC_STR - pr_warningln "\nNo cipher mapping file found " debugme echo "$CIPHERS_BY_STRENGTH_FILE" - pr_warningln "Please note from 2.9dev on testssl.sh needs some files in \$TESTSSL_INSTALL_DIR/etc to function correctly" - ignore_no_or_lame "Type \"yes\" to ignore " + pr_warningln "\nATTENTION: No cipher mapping file found!" + outln "Please note from 2.9dev on $PROG_NAME needs files in \"\$TESTSSL_INSTALL_DIR/etc/\" to function correctly." + outln + ignore_no_or_lame "Type \"yes\" to ignore this warning and proceed at your own risk" "yes" [[ $? -ne 0 ]] && exit -2 fi } @@ -9632,7 +9633,7 @@ find_openssl_binary() { fi else outln - ignore_no_or_lame " neccessary binary \"timeout\" not found. Continue without timeout?" + ignore_no_or_lame " Neccessary binary \"timeout\" not found. Continue without timeout? " "y" [[ $? -ne 0 ]] && exit -2 unset OPENSSL_TIMEOUT fi @@ -9662,7 +9663,7 @@ check4openssl_oldfarts() { *) outln " Update openssl binaries or compile from github.com/PeterMosmans/openssl" fileout "too_old_openssl" "WARN" "Update openssl binaries or compile from github.com/PeterMosmans/openssl .";; esac - ignore_no_or_lame " Type \"yes\" to accept false negatives or positives " + ignore_no_or_lame " Type \"yes\" to accept false negatives or positives" "yes" [[ $? -ne 0 ]] && exit -2 fi outln @@ -10020,20 +10021,21 @@ EOF return 0 } - +# arg1: text to display before "-->" +# arg2: arg needed to accept to continue ignore_no_or_lame() { local a [[ "$WARNINGS" == off ]] && return 0 [[ "$WARNINGS" == false ]] && return 0 [[ "$WARNINGS" == batch ]] && return 1 - pr_magenta "$1 " + pr_warning "$1 --> " read a - case $a in - Y|y|Yes|YES|yes) return 0;; - default) ;; - esac - return 1 + if [[ "$a" == "$(tolower "$2")" ]]; then + $ok_arg return 0 + else + return 1 + fi } # arg1: URI @@ -10445,7 +10447,7 @@ determine_optimal_proto() { debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO" if [[ "$OPTIMAL_PROTO" == "-ssl2" ]]; then pr_magentaln "$NODEIP:$PORT appears to only support SSLv2." - ignore_no_or_lame " Type \"yes\" to accept some false negatives or positives " + ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes" [[ $? -ne 0 ]] && exit -2 fi fi @@ -10460,7 +10462,7 @@ determine_optimal_proto() { fi tmpfile_handle $FUNCNAME.txt pr_boldln "doesn't seem to be a TLS/SSL enabled server"; - ignore_no_or_lame " Note that the results might look ok but they are nonsense. Proceed ? " + ignore_no_or_lame " The results might look ok but they could be nonsense. Really proceed ? (\"yes\" to continue)" "yes" [[ $? -ne 0 ]] && exit -2 fi @@ -11252,10 +11254,10 @@ lets_roll() { ################# main ################# -get_install_dir initialize_globals parse_cmd_line "$@" +get_install_dir set_color_functions maketempf find_openssl_binary