diff --git a/cipherscan b/cipherscan index 1448763..7804f13 100755 --- a/cipherscan +++ b/cipherscan @@ -63,6 +63,8 @@ CAPATH="" SAVECRT="" TEST_CURVES="False" has_curves="False" +# openssl formated list of curves that will cause server to select ECC suite +ecc_ciphers="" unset known_certs declare -A known_certs unset cert_checksums @@ -458,7 +460,11 @@ test_cipher_on_target() { if [[ $pfs =~ ECDH ]]; then has_curves="True" if [ $TEST_CURVES == "True" ]; then - test_ecc_curves + test_curves + if [ "$ecc_ciphers" != "" ]; then + ecc_ciphers+=":" + fi + ecc_ciphers+="$cipher" else # resolve the openssl curve to the proper IANA name current_curves="$(get_curve_name $(echo $pfs|cut -d ',' -f2))" @@ -622,6 +628,7 @@ display_results_in_terminal() { fi if [ $TEST_CURVES == "True" ]; then echo "Curves ordering: $curvesordering" + echo "Curves fallback: $fallback_supported" fi } @@ -656,7 +663,11 @@ display_results_in_json() { echo -n "}" ctr=$((ctr+1)) done - echo ']}' + echo -n ']' + if [ $TEST_CURVES == "True" ]; then + echo -n ",\"curves_fallback\":\"$fallback_supported\"" + fi + echo '}' } test_serverside_ordering() { @@ -712,9 +723,7 @@ test_serverside_ordering() { fi } -test_ecc_curves() { - # openssl formated list of curves that will cause server to select ECC suite - local ecc_ciphers="" +test_curves() { # "True" if server supports ciphers that don't use ECC at a lower priority local fallback_available="False" @@ -817,8 +826,7 @@ test_ecc_curves() { local tmp=$(echo Q | $sslcommand -curves $test_curves 2>/dev/null) parse_openssl_output <<<"$tmp" - if [[ -z $current_protocol || $current_cipher == "(NONE)" \ - || $current_cipher == '0000' ]]; then + if [[ -z $current_protocol || $current_cipher == "(NONE)" || $current_cipher == '0000' ]]; then fallback_supported="order-specific" verbose "Server aborted connection" else @@ -829,18 +837,14 @@ test_ecc_curves() { if [[ ${ephem_data[0]} =~ ECDH ]]; then verbose "Server did select ${ephem_data[1]} curve" curves_ordering="inconclusive-${ephem_data[1]}" - for id in "${!curve_names[@]}"; do - if [[ ${curve_names[$id]} =~ ${ephem_data[1]} ]]; then - local canonic_name=(${curve_names[$id]}) - if [[ ${canonic_name[0]} == $most_wanted ]]; then - curves_ordering="client" - break - else - curves_ordering="server" - break - fi - fi - done + local cname="$(get_curve_name ${ephem_data[1]})" + if [ "$cname" == "$most_wanted" ]; then + curves_ordering="client" + break + else + curves_ordering="server" + break + fi else # some servers downgrade to non ECDH when curve order is changed curves_ordering="inconclusive-noecc" @@ -849,6 +853,76 @@ test_ecc_curves() { fi } +test_curves_fallback() { + # "True" if server supports ciphers that don't use ECC at a lower priority + local fallback_available="False" + # return variable: whatever a server will fall back to non ECC suite when + # client doesn't advertise support for curves the server needs + fallback_supported="False" + + # prepare the ssl command we'll be using + local sslcommand="" + sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client" + if [ -n "$CAPATH" ]; then + sslcommand+=" -CApath $CAPATH -showcerts" + elif [ -e "$CACERTS" ]; then + sslcommand+=" -CAfile $CACERTS" + fi + sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $ecc_ciphers" + # force the TLS to send a TLS1.0 client hello at least, as with SSLv2 + # ciphers present it will try to send a SSLv2 compatible client hello + sslcommand+=" -no_ssl2 -no_ssl3" + + # + # here we use the same logic as with detecting cipher suites: first + # advertise all curves as supported, then remove curves one by one until we + # either get a fallback to a non ECC cipher, we run of curves or server + # tries to negotiate a curve we didn't advertise + # + local curves=(${CURVES[*]}) + while [[ ${#curves[@]} -gt 0 ]]; do + OLDIFS="$IFS" + IFS=':' + local test_curves="${curves[*]}" + IFS="$OLDIFS" + verbose "Testing $test_curves" + + ratelimit + local tmp=$(echo Q | $sslcommand -curves $test_curves 2>/dev/null) + parse_openssl_output <<<"$tmp" + + if [[ -z $current_protocol || $current_cipher == "(NONE)" || $current_cipher == '0000' ]]; then + # server aborted connection + if [[ $fallback_available == "True" ]]; then + fallback_supported="False" + else + fallback_supported="unknown" + fi + break + else + # server accepted connection + local ephem_data=(${current_pfs//,/ }) + + if [[ ${ephem_data[0]} =~ ECDH ]]; then + # we got an ecc connection, remove the curve from the list of testable curves + local cname="$(get_curve_name ${ephem_data[1]})" + verbose "Server selected curve $cname" + for id in "${!curves[@]}"; do + if [ "${curves[id]}" == "$cname" ]; then + unset curves[$id] + break + fi + done + else + verbose "Server fell back to $current_cipher" + # ok, we got a fallback + fallback_supported="True" + break + fi + fi + done +} + # If no options are given, give usage information and exit (with error code) if [ $# -eq 0 ]; then usage; @@ -979,7 +1053,7 @@ get_cipher_pref $CIPHERSUITE test_serverside_ordering if [[ $TEST_CURVES == "True" ]]; then - test_ecc_curves + test_curves_fallback fi if [ "$OUTPUTFORMAT" == "json" ]; then