From c52e008347d84526444718732440887c716b91f9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 6 Nov 2014 01:43:05 +0100 Subject: [PATCH 1/9] add support for testing supported curves since early versions of 1.0.2 openssl supports -curves command line option, it allows us to set the curves advertised as supported use the same approach to testing: advertise all, check what server accepts, remove the accepted from list, repeat. When server aborts connection or selects non ECC cipher, we know that we've tested all. --- cipherscan | 280 ++++++++++++++++++++++++++++++++++++++++- top1m/parse_results.py | 40 ++++++ top1m/testtop1m.sh | 4 +- 3 files changed, 321 insertions(+), 3 deletions(-) diff --git a/cipherscan b/cipherscan index 46bb981..409b349 100755 --- a/cipherscan +++ b/cipherscan @@ -61,6 +61,7 @@ TIMEOUT=30 # trust anchors are stored CAPATH="" SAVECRT="" +TEST_CURVES="False" unset known_certs declare -A known_certs unset cert_checksums @@ -99,6 +100,7 @@ Use one of the options below: -j | --json Output results in JSON format. -o | --openssl path/to/your/openssl binary you want to use. --savecrt path where to save untrusted and leaf certificates +--curves test ECC curves supported by server (req. OpenSSL 1.0.2) -v | --verbose Increase verbosity. The rest of the arguments will be interpreted as openssl s_client argument. @@ -529,6 +531,25 @@ display_results_in_terminal() { else echo "Client side cipher ordering" fi + + if [[ $TEST_CURVES == "True" ]]; then + if [[ -n $supported_curves ]]; then + echo + local i=0 + (echo "prio curve" + for curve in ${supported_curves//,/ }; do + i=$((i+1)) + echo "$i $curve" + done) | column -t + echo + if [[ $fallback_supported == True ]]; then + echo "Server does fallback on unsupported curves" + else + echo "Server doesn't fallback on unsupported curves" + fi + echo "$curves_ordering side curve ordering" + fi + fi } display_results_in_json() { @@ -553,7 +574,14 @@ display_results_in_json() { echo -n "\"pfs\":\"$pfs\"}" ctr=$((ctr+1)) done - echo ']}' + echo -n ']' + if [[ -n $supported_curves ]]; then + echo -n "," + echo -n "\"curve_fallback\":\"$fallback_supported\"," + echo -n "\"curve_ordering\":\"$curves_ordering\"," + echo -n "\"curve\":[\"${supported_curves//,/\",\"}\"]" + fi + echo '}' } test_serverside_ordering() { @@ -609,6 +637,248 @@ test_serverside_ordering() { fi } +test_ecc_curves() { + # openssl formated list of curves that will cause server to select ECC suite + local ecc_ciphers="" + # names of all curves supported in TLS (as supported by openssl) + local curves=() + # alternative names for curves in TLS (as returned by openssl s_client or + # as specified in standard) + local curve_names=() + # "True" if server supports ciphers that don't use ECC at a lower priority + local fallback_available="False" + + # return variable: list of curves supported by server, in order + supported_curves="" + # 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" + # return variable: check if server uses server side or client side ordering + # for curves + curves_ordering="server" + + # get ciphers that will cause server to select suite that uses ECC, if most + # preferred ciphers do not, exclude them from list + for pref in "${cipherspref[@]}"; do + # get first value from space separated array + cipher=($pref) + + # ECDH uses curve from certificate, so no way to negotiate it, the two + # below are ephemeral so they can select the curve independent of cert + if [[ $cipher =~ ECDHE ]] || [[ $cipher =~ AECDH ]]; then + # colon on end of string is ignored by openssl + ecc_ciphers+="${cipher}:" + elif [[ -n $ecc_ciphers ]]; then + # add fallback ciphers + ecc_ciphers+="${cipher}:" + fallback_available="True" + fi + done + + if [[ -z $ecc_ciphers ]]; then + verbose "Server does not support ephemeral ECC" + return + fi + + verbose "ECC curves testing, cipher preference: $ecc_ciphers" + + # only some curves have defined TLS code points, so no use parsing openssl + # output checking what it supports, see + # http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 + curves=(sect163k1 # K-163 + sect163r1 + sect163r2 # B-163 + sect193r1 + sect193r2 + sect233k1 # K-233 + sect233r1 # B-233 + sect239k1 + sect283k1 # K-283 + sect283r1 # B-283 + sect409k1 # K-409 + sect409r1 # B-409 + sect571k1 # K-571 + sect571r1 # B-571 + secp160k1 + secp160r1 + secp160r2 + secp192k1 + prime192v1 # P-192 secp192r1 + secp224k1 + secp224r1 # P-224 + secp256k1 + prime256v1 # P-256 secp256r1 + secp384r1 # P-384 + secp521r1 # P-521 + brainpoolP256r1 + brainpoolP384r1 + brainpoolP512r1) + + # many curves have alternative names, save those too + curve_names=("sect163k1 K-163" + "sect163r1" + "sect163r2 B-163" + "sect193r1" + "sect193r2" + "sect233k1 K-233" + "sect233r1 B-233" + "sect239k1" + "sect283k1 K-283" + "sect283r1 B-283" + "sect409k1 K-409" + "sect409r1 B-409" + "sect571k1 K-571" + "sect571r1 B-571" + "secp160k1" + "secp160r1" + "secp160r2" + "secp192k1" + "prime192v1 P-192 secp192r1" + "secp224k1" + "secp224r1 P-224" + "secp256k1" + "prime256v1 P-256 secp256r1" + "secp384r1 P-384" + "secp521r1 P-521" + "brainpoolP256r1" + "brainpoolP384r1" + "brainpoolP512r1") + + OLDIFS="$IFS" + IFS=':' + verbose "Will test following curves: ${curves[*]}" + IFS="$OLDIFS" + + # 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" + + # + # 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 + # + 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 + # ok, we got an ECC connection, now, what curve did we get + for id in "${!curves[@]}"; do + # compare to alternative names + if [[ ${curve_names[$id]} =~ ${ephem_data[1]} ]]; then + if [[ -n $supported_curves ]]; then + supported_curves+="," + fi + supported_curves+="${curves[$id]}" + verbose "Server selected ${ephem_data[1]}, a.k.a ${curves[$id]}" + # ok, we know it's supported, remove it from set of offered ones + 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 + + [ "$OUTPUTFORMAT" == "terminal" ] && [ $DEBUG -lt 1 ] && echo -n '.' + done + + # don't penalize servers that will negotiate all curves we know of... + if [[ ${#curves[@]} -eq 0 ]]; then + fallback_supported="unknown" + fi + + # + # check if curves ordering is server of client side + # + + local tmp_curves=(${supported_curves//,/ }) + verbose "Server supported curves: ${tmp_curves[@]}" + + # server supports just one or none, so it effectively uses server side + # ordering (as it dictates what curves client must support) + if [ ${#tmp_curves[@]} -lt 2 ]; then + curves_ordering="server" + else + # server supports at least 2 curves, rotate their order, see if + # selected changes + test_curves="" + most_wanted="${tmp_curves[${#tmp_curves[@]}-1]}" + for (( i=${#tmp_curves[@]}-1; i>0; i--)); do + test_curves+="${tmp_curves[$i]}:" + done + test_curves+="${tmp_curves[0]}" + + verbose "Testing ordering with $sslcommand -curves $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 + fallback_supported="order-specific" + verbose "Server aborted connection" + else + local ephem_data=(${current_pfs//,/ }) + verbose "Server selected $current_cipher with $current_pfs" + verbose "ephem_data: ${ephem_data[@]}" + + 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 + else + # some servers downgrade to non ECDH when curve order is changed + curves_ordering="inconclusive-noecc" + fi + fi + fi +} + # If no options are given, give usage information and exit (with error code) if [ $# -eq 0 ]; then usage; @@ -664,6 +934,10 @@ do SAVECRT="$2" shift 2 ;; + --curves) + TEST_CURVES="True" + shift 1 + ;; --) # End of all options shift break @@ -723,6 +997,10 @@ get_cipher_pref $CIPHERSUITE test_serverside_ordering +if [[ $TEST_CURVES == "True" ]]; then + test_ecc_curves +fi + if [ "$OUTPUTFORMAT" == "json" ]; then display_results_in_json else diff --git a/top1m/parse_results.py b/top1m/parse_results.py index 0da5e42..8e69005 100644 --- a/top1m/parse_results.py +++ b/top1m/parse_results.py @@ -72,6 +72,9 @@ handshakestats = defaultdict(int) keysize = defaultdict(int) sigalg = defaultdict(int) tickethint = defaultdict(int) +eccfallback = defaultdict(int) +eccordering = defaultdict(int) +ecccurve = defaultdict(int) ocspstaple = defaultdict(int) dsarsastack = 0 total = 0 @@ -86,6 +89,9 @@ for r,d,flist in os.walk(path): tempdsakeystats = {} tempsigstats = {} tempticketstats = {} + tempeccfallback = "unknown" + tempeccordering = "unknown" + tempecccurve = {} """ supported ciphers by the server under scan """ tempcipherstats = {} ciphertypes = 0 @@ -141,6 +147,17 @@ for r,d,flist in os.walk(path): if len(results['ciphersuite']) < 1: continue + """ save ECC curve stats """ + if 'curve_fallback' in results: + tempeccfallback = results['curve_fallback'] + if 'curve_ordering' in results: + tempeccordering = results['curve_ordering'] + if 'curve' in results: + for curve in results['curve']: + tempecccurve[curve] = 1 + if len(results['curve']) == 1: + tempecccurve[curve + ' Only'] = 1 + """ loop over list of ciphers """ for entry in results['ciphersuite']: @@ -329,6 +346,11 @@ for r,d,flist in os.walk(path): for s in tempticketstats: tickethint[s] += 1 + eccfallback[tempeccfallback] += 1 + eccordering[tempeccordering] += 1 + for s in tempecccurve: + ecccurve[s] += 1 + if ocsp_stapling is None: ocspstaple['Unknown'] += 1 elif ocsp_stapling: @@ -518,6 +540,24 @@ for stat in sorted(pfsstats): pfspercent = round(pfsstats[stat] / handshakestats['DHE'] * 100, 4) sys.stdout.write(stat.ljust(25) + " " + str(pfsstats[stat]).ljust(10) + str(percent).ljust(9) + str(pfspercent) + "\n") +print("\nSupported ECC curves Count Percent ") +print("-------------------------+---------+--------") +for stat in sorted(ecccurve): + percent = round(ecccurve[stat] / total * 100, 4) + sys.stdout.write(stat.ljust(25) + " " + str(ecccurve[stat]).ljust(10) + str(percent).ljust(9) + "\n") + +print("\nUnsupported curve fallback Count Percent ") +print("------------------------------+---------+--------") +for stat in sorted(eccfallback): + percent = round(eccfallback[stat] / total * 100,4) + sys.stdout.write(stat.ljust(30) + " " + str(eccfallback[stat]).ljust(10) + str(percent).ljust(9) + "\n") + +print("\nECC curve ordering Count Percent ") +print("-------------------------+---------+--------") +for stat in sorted(eccordering): + percent = round(eccordering[stat] / total * 100, 4) + sys.stdout.write(stat.ljust(25) + " " + str(eccordering[stat]).ljust(10) + str(percent).ljust(9) + "\n") + print("\nTLS session ticket hint Count Percent ") print("-------------------------+---------+--------") for stat in natural_sort(tickethint): diff --git a/top1m/testtop1m.sh b/top1m/testtop1m.sh index 8a39055..faf8f26 100755 --- a/top1m/testtop1m.sh +++ b/top1m/testtop1m.sh @@ -68,7 +68,7 @@ function scan_host() { if [ $? -gt 0 ]; then return fi - ../cipherscan --capath ca_files --saveca --savecrt certs --delay 2 -json -servername $1 $2:443 > results/$1@$2 + ../cipherscan --capath ca_files --saveca --curves --savecrt certs --delay 2 -json -servername $1 $2:443 > results/$1@$2 } function scan_host_no_sni() { @@ -80,7 +80,7 @@ function scan_host_no_sni() { if [ $? -gt 0 ]; then return fi - ../cipherscan --capath ca_files --saveca --savecrt certs --delay 2 -json $1:443 > results/$1 + ../cipherscan --capath ca_files --saveca --curves --savecrt certs --delay 2 -json $1:443 > results/$1 } function scan_hostname() { From 224227cc5e36c3d303f1c5898220fbdc8d839cbe Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 7 Nov 2014 02:21:29 +0100 Subject: [PATCH 2/9] force at least TLSv1.0 in curves tolerance test because to advertise curves to server we need extensions and extensions are only available in TLSv1.0 or later, we need to force OpenSSL not to send SSLv2 compatible hello if it thinks it's ok to do (when there are SSLv2 ciphers present in cipherstring it will try to) --- cipherscan | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cipherscan b/cipherscan index 409b349..93d96d1 100755 --- a/cipherscan +++ b/cipherscan @@ -758,6 +758,9 @@ test_ecc_curves() { 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 From cc014f085d918b617aaa13238906f1ac3b49f980 Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Fri, 27 Mar 2015 19:03:27 -0400 Subject: [PATCH 3/9] test curve for each ECDH cipher, change PFS output to use curve name --- cipherscan | 304 +++++++++++++++++++++++------------------------------ 1 file changed, 132 insertions(+), 172 deletions(-) diff --git a/cipherscan b/cipherscan index 93d96d1..cd40afd 100755 --- a/cipherscan +++ b/cipherscan @@ -100,7 +100,7 @@ Use one of the options below: -j | --json Output results in JSON format. -o | --openssl path/to/your/openssl binary you want to use. --savecrt path where to save untrusted and leaf certificates ---curves test ECC curves supported by server (req. OpenSSL 1.0.2) +--[no-]curves test ECC curves supported by server (req. OpenSSL 1.0.2) -v | --verbose Increase verbosity. The rest of the arguments will be interpreted as openssl s_client argument. @@ -123,6 +123,68 @@ debug(){ fi } +# obtain an array of curves supported by openssl +CURVES=(sect163k1 # K-163 + sect163r1 + sect163r2 # B-163 + sect193r1 + sect193r2 + sect233k1 # K-233 + sect233r1 # B-233 + sect239k1 + sect283k1 # K-283 + sect283r1 # B-283 + sect409k1 # K-409 + sect409r1 # B-409 + sect571k1 # K-571 + sect571r1 # B-571 + secp160k1 + secp160r1 + secp160r2 + secp192k1 + prime192v1 # P-192 secp192r1 + secp224k1 + secp224r1 # P-224 + secp256k1 + prime256v1 # P-256 secp256r1 + secp384r1 # P-384 + secp521r1 # P-521 + brainpoolP256r1 + brainpoolP384r1 + brainpoolP512r1) + +# many curves have alternative names, this array provides a mapping to find the IANA +# name of a curve using its alias +CURVES_MAP=("sect163k1 K-163" + "sect163r2 B-163" + "sect233k1 K-233" + "sect233r1 B-233" + "sect283k1 K-283" + "sect283r1 B-283" + "sect409k1 K-409" + "sect409r1 B-409" + "sect571k1 K-571" + "sect571r1 B-571" + "prime192v1 P-192 secp192r1" + "secp224r1 P-224" + "prime256v1 P-256 secp256r1" + "secp384r1 P-384" + "secp521r1 P-521") + +get_curve_name() { + local identifier=$1 + for c in "${CURVES_MAP[@]}"; do + if [[ "$c" =~ $identifier ]]; then + verbose "$c matches identifier $identifier" + local map=(${c// / }) + echo ${map[0]} + return + fi + done + echo $identifier + return +} + c_hash() { local h=$(${OPENSSLBIN} x509 -hash -noout -in "$1/$2" 2>/dev/null) for ((num=0; num<=100; num++)) ; do @@ -366,6 +428,7 @@ test_cipher_on_target() { fi cipher=$current_cipher pfs=$current_pfs + [ -z $pfs ] && pfs="None" pubkey=$current_pubkey sigalg=$current_sigalg trusted=$current_trusted @@ -383,13 +446,23 @@ test_cipher_on_target() { # if cipher contains NONE, the cipher wasn't accepted elif [ "$cipher" == '(NONE) ' ]; then - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs" + result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $curves_ordering" verbose "handshake failed, server returned ciphersuite '$result'" return 1 # the connection succeeded else - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs" + # if pfs uses ECDH, test supported curves + if [[ $pfs =~ ECDH ]]; then + if [ $TEST_CURVES == "True" ]; then + test_ecc_curves + pfs=$current_curves + else + # resolve the openssl curve to the proper IANA name + pfs="$(get_curve_name $(echo $pfs|cut -d ',' -f2))" + fi + fi + result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $curves_ordering" verbose "handshake succeeded, server returned ciphersuite '$result'" return 0 fi @@ -481,23 +554,24 @@ display_results_in_terminal() { if [ "$tickethint" != "${cipher_data[5]}" ]; then different=True fi + if [ "$ocspstaple" != "${cipher_data[6]}" ]; then + different=True + fi fi results=("${results[@]}" "$r") ctr=$((ctr+1)) done + header="prio ciphersuite protocols" + if [ $different == "True" ]; then + header="$header pubkey_size signature_algoritm trusted ticket_hint ocsp_staple" + fi + header="$header pfs" + if [ $TEST_CURVES == "True" ]; then + header="$header curves_ordering" + fi if [ $DOBENCHMARK -eq 1 ]; then - if [ $different == "True" ]; then - header="prio ciphersuite protocols pubkey_size signature_algoritm trusted ticket_hint ocsp_staple pfs_keysize avg_handshake_microsec" - else - header="prio ciphersuite protocols pfs_keysize avg_handshake_microsec" - fi - else - if [ $different == "True" ]; then - header="prio ciphersuite protocols pubkey_size signature_algorithm trusted ticket_hint ocsp_staple pfs_keysize" - else - header="prio ciphersuite protocols pfs_keysize" - fi + header="$header avg_handshake_microsec" fi ctr=0 for result in "${results[@]}"; do @@ -508,7 +582,7 @@ display_results_in_terminal() { if [ $different == "True" ]; then echo $result|grep -v '(NONE)' else - # prints priority, ciphersuite, protocols and pfs_keysize + # prints priority, ciphersuite, protocols and pfs awk '!/(NONE)/{print $1 " " $2 " " $3 " " $9}' <<<"$result" fi done|column -t @@ -531,25 +605,6 @@ display_results_in_terminal() { else echo "Client side cipher ordering" fi - - if [[ $TEST_CURVES == "True" ]]; then - if [[ -n $supported_curves ]]; then - echo - local i=0 - (echo "prio curve" - for curve in ${supported_curves//,/ }; do - i=$((i+1)) - echo "$i $curve" - done) | column -t - echo - if [[ $fallback_supported == True ]]; then - echo "Server does fallback on unsupported curves" - else - echo "Server doesn't fallback on unsupported curves" - fi - echo "$curves_ordering side curve ordering" - fi - fi } display_results_in_json() { @@ -571,17 +626,19 @@ display_results_in_json() { echo -n "\"ocsp_stapling\":\"${cipher_arr[6]}\"," pfs="${cipher_arr[7]}" [ "$pfs" == "" ] && pfs="None" - echo -n "\"pfs\":\"$pfs\"}" + echo -n "\"pfs\":\"$pfs\"" + if [[ "${cipher_arr[0]}" =~ ECDH ]]; then + echo -n "," + echo -n "\"elliptic_curves\":[\"${pfs//,/\",\"}\"]" + if [ $TEST_CURVES == "True" ]; then + echo -n "," + echo -n "\"curves_ordering\":\"${cipher_arr[8]}\"" + fi + fi + echo -n "}" ctr=$((ctr+1)) done - echo -n ']' - if [[ -n $supported_curves ]]; then - echo -n "," - echo -n "\"curve_fallback\":\"$fallback_supported\"," - echo -n "\"curve_ordering\":\"$curves_ordering\"," - echo -n "\"curve\":[\"${supported_curves//,/\",\"}\"]" - fi - echo '}' + echo ']}' } test_serverside_ordering() { @@ -640,109 +697,16 @@ test_serverside_ordering() { test_ecc_curves() { # openssl formated list of curves that will cause server to select ECC suite local ecc_ciphers="" - # names of all curves supported in TLS (as supported by openssl) - local curves=() - # alternative names for curves in TLS (as returned by openssl s_client or - # as specified in standard) - local curve_names=() # "True" if server supports ciphers that don't use ECC at a lower priority local fallback_available="False" # return variable: list of curves supported by server, in order - supported_curves="" - # 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" + current_curves="" # return variable: check if server uses server side or client side ordering # for curves curves_ordering="server" - # get ciphers that will cause server to select suite that uses ECC, if most - # preferred ciphers do not, exclude them from list - for pref in "${cipherspref[@]}"; do - # get first value from space separated array - cipher=($pref) - - # ECDH uses curve from certificate, so no way to negotiate it, the two - # below are ephemeral so they can select the curve independent of cert - if [[ $cipher =~ ECDHE ]] || [[ $cipher =~ AECDH ]]; then - # colon on end of string is ignored by openssl - ecc_ciphers+="${cipher}:" - elif [[ -n $ecc_ciphers ]]; then - # add fallback ciphers - ecc_ciphers+="${cipher}:" - fallback_available="True" - fi - done - - if [[ -z $ecc_ciphers ]]; then - verbose "Server does not support ephemeral ECC" - return - fi - - verbose "ECC curves testing, cipher preference: $ecc_ciphers" - - # only some curves have defined TLS code points, so no use parsing openssl - # output checking what it supports, see - # http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 - curves=(sect163k1 # K-163 - sect163r1 - sect163r2 # B-163 - sect193r1 - sect193r2 - sect233k1 # K-233 - sect233r1 # B-233 - sect239k1 - sect283k1 # K-283 - sect283r1 # B-283 - sect409k1 # K-409 - sect409r1 # B-409 - sect571k1 # K-571 - sect571r1 # B-571 - secp160k1 - secp160r1 - secp160r2 - secp192k1 - prime192v1 # P-192 secp192r1 - secp224k1 - secp224r1 # P-224 - secp256k1 - prime256v1 # P-256 secp256r1 - secp384r1 # P-384 - secp521r1 # P-521 - brainpoolP256r1 - brainpoolP384r1 - brainpoolP512r1) - - # many curves have alternative names, save those too - curve_names=("sect163k1 K-163" - "sect163r1" - "sect163r2 B-163" - "sect193r1" - "sect193r2" - "sect233k1 K-233" - "sect233r1 B-233" - "sect239k1" - "sect283k1 K-283" - "sect283r1 B-283" - "sect409k1 K-409" - "sect409r1 B-409" - "sect571k1 K-571" - "sect571r1 B-571" - "secp160k1" - "secp160r1" - "secp160r2" - "secp192k1" - "prime192v1 P-192 secp192r1" - "secp224k1" - "secp224r1 P-224" - "secp256k1" - "prime256v1 P-256 secp256r1" - "secp384r1 P-384" - "secp521r1 P-521" - "brainpoolP256r1" - "brainpoolP384r1" - "brainpoolP512r1") + local curves=(${CURVES[*]}) OLDIFS="$IFS" IFS=':' @@ -757,7 +721,7 @@ test_ecc_curves() { elif [ -e "$CACERTS" ]; then sslcommand+=" -CAfile $CACERTS" fi - sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $ecc_ciphers" + sslcommand+=" -status $SCLIENTARGS -connect $TARGET -cipher $current_cipher" # 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" @@ -773,49 +737,34 @@ test_ecc_curves() { IFS=':' local test_curves="${curves[*]}" IFS="$OLDIFS" - verbose "Testing $test_curves" + verbose "Testing $test_curves with command $sslcommand" 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 + if [[ -z $current_protocol || $current_cipher == "(NONE)" || $current_cipher == '0000' ]]; then + break else # server accepted connection local ephem_data=(${current_pfs//,/ }) - + local cname="" if [[ ${ephem_data[0]} =~ ECDH ]]; then - # ok, we got an ECC connection, now, what curve did we get - for id in "${!curves[@]}"; do - # compare to alternative names - if [[ ${curve_names[$id]} =~ ${ephem_data[1]} ]]; then - if [[ -n $supported_curves ]]; then - supported_curves+="," - fi - supported_curves+="${curves[$id]}" - verbose "Server selected ${ephem_data[1]}, a.k.a ${curves[$id]}" - # ok, we know it's supported, remove it from set of offered ones - unset curves[$id] - break - fi - done - - else - verbose "Server fell back to $current_cipher" - # ok, we got a fallback - fallback_supported="True" - break + if [ "$current_curves" != "" ]; then + current_curves+="," + fi + cname="$(get_curve_name ${ephem_data[1]})" + verbose "Server selected ${ephem_data[1]}, a.k.a $cname" + current_curves+="$cname" fi + for id in "${!curves[@]}"; do + if [ "$cname" == ${curves[$id]} ]; then + # we know it's supported, remove it from set of offered ones + unset curves[$id] + break + fi + done fi - [ "$OUTPUTFORMAT" == "terminal" ] && [ $DEBUG -lt 1 ] && echo -n '.' done @@ -828,7 +777,7 @@ test_ecc_curves() { # check if curves ordering is server of client side # - local tmp_curves=(${supported_curves//,/ }) + local tmp_curves=(${current_curves//,/ }) verbose "Server supported curves: ${tmp_curves[@]}" # server supports just one or none, so it effectively uses server side @@ -941,6 +890,10 @@ do TEST_CURVES="True" shift 1 ;; + --no-curves) + TEST_CURVES="False" + shift 1 + ;; --) # End of all options shift break @@ -981,6 +934,13 @@ if [ ! -x $OPENSSLBIN ]; then fi fi +if [ $TEST_CURVES == "True" ]; then + if [ ! -z "$($OPENSSLBIN s_client -curves 2>&1|head -1|grep 'unknown option')" ]; then + echo "curves testing not available with your version of openssl, disabling it" + TEST_CURVES="False" + fi +fi + if [ $VERBOSE != 0 ] ; then [ -n "$CACERTS" ] && echo "Using trust anchors from $CACERTS" echo "Loading $($OPENSSLBIN ciphers -v $CIPHERSUITE 2>/dev/null|grep Kx|wc -l) ciphersuites from $(echo -n $($OPENSSLBIN version 2>/dev/null))" From c90e5c59d7bc9dc9ad5e019be40649676c6ed455 Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Wed, 1 Apr 2015 11:18:31 -0400 Subject: [PATCH 4/9] Improve output of curves --- cipherscan | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/cipherscan b/cipherscan index cd40afd..1448763 100755 --- a/cipherscan +++ b/cipherscan @@ -62,6 +62,7 @@ TIMEOUT=30 CAPATH="" SAVECRT="" TEST_CURVES="False" +has_curves="False" unset known_certs declare -A known_certs unset cert_checksums @@ -446,23 +447,24 @@ test_cipher_on_target() { # if cipher contains NONE, the cipher wasn't accepted elif [ "$cipher" == '(NONE) ' ]; then - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $curves_ordering" + result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" verbose "handshake failed, server returned ciphersuite '$result'" return 1 # the connection succeeded else + current_curves="None" # if pfs uses ECDH, test supported curves if [[ $pfs =~ ECDH ]]; then + has_curves="True" if [ $TEST_CURVES == "True" ]; then test_ecc_curves - pfs=$current_curves else # resolve the openssl curve to the proper IANA name - pfs="$(get_curve_name $(echo $pfs|cut -d ',' -f2))" + current_curves="$(get_curve_name $(echo $pfs|cut -d ',' -f2))" fi fi - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $curves_ordering" + result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" verbose "handshake succeeded, server returned ciphersuite '$result'" return 0 fi @@ -523,6 +525,7 @@ display_results_in_terminal() { local trusted local tickethint local ocspstaple + local curvesordering local different=False echo "Target: $TARGET"; echo for cipher in "${cipherspref[@]}"; do @@ -541,6 +544,9 @@ display_results_in_terminal() { trusted="${cipher_data[4]}" tickethint="${cipher_data[5]}" ocspstaple="${cipher_data[6]}" + if [[ $TEST_CURVES == "True" && "${cipher_data[9]}" != "" ]]; then + curvesordering="${cipher_data[9]}" + fi else if [ "$pubkey" != "${cipher_data[2]}" ]; then different=True @@ -557,6 +563,12 @@ display_results_in_terminal() { if [ "$ocspstaple" != "${cipher_data[6]}" ]; then different=True fi + if [[ "$curvesordering" == "" && "${cipher_data[9]}" != "" ]]; then + curvesordering="${cipher_data[9]}" + fi + if [[ "$curvesordering" != "" && "$curvesordering" != "${cipher_data[9]}" ]]; then + different=True + fi fi results=("${results[@]}" "$r") ctr=$((ctr+1)) @@ -564,14 +576,17 @@ display_results_in_terminal() { header="prio ciphersuite protocols" if [ $different == "True" ]; then - header="$header pubkey_size signature_algoritm trusted ticket_hint ocsp_staple" + header+=" pubkey_size signature_algoritm trusted ticket_hint ocsp_staple" fi - header="$header pfs" - if [ $TEST_CURVES == "True" ]; then - header="$header curves_ordering" + header+=" pfs" + if [ $has_curves == "True" ]; then + header+=" curves" + if [[ $TEST_CURVES == "True" && $different == "True" ]]; then + header+=" curves_ordering" + fi fi if [ $DOBENCHMARK -eq 1 ]; then - header="$header avg_handshake_microsec" + header+=" avg_handshake_microsec" fi ctr=0 for result in "${results[@]}"; do @@ -583,7 +598,7 @@ display_results_in_terminal() { echo $result|grep -v '(NONE)' else # prints priority, ciphersuite, protocols and pfs - awk '!/(NONE)/{print $1 " " $2 " " $3 " " $9}' <<<"$result" + awk '!/(NONE)/{print $1 " " $2 " " $3 " " $9 " " $10}' <<<"$result" fi done|column -t echo @@ -601,9 +616,12 @@ display_results_in_terminal() { echo "OCSP stapling: not supported" fi if [[ $serverside == "True" ]]; then - echo "Server side cipher ordering" + echo "Cipher ordering: server" else - echo "Client side cipher ordering" + echo "Cipher ordering: client" + fi + if [ $TEST_CURVES == "True" ]; then + echo "Curves ordering: $curvesordering" fi } @@ -629,10 +647,10 @@ display_results_in_json() { echo -n "\"pfs\":\"$pfs\"" if [[ "${cipher_arr[0]}" =~ ECDH ]]; then echo -n "," - echo -n "\"elliptic_curves\":[\"${pfs//,/\",\"}\"]" + echo -n "\"curves\":[\"${cipher_arr[8]//,/\",\"}\"]" if [ $TEST_CURVES == "True" ]; then echo -n "," - echo -n "\"curves_ordering\":\"${cipher_arr[8]}\"" + echo -n "\"curves_ordering\":\"${cipher_arr[9]}\"" fi fi echo -n "}" From 04314bffdc7dd8e5ada701435107b49e9e660b4b Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Wed, 1 Apr 2015 11:18:41 -0400 Subject: [PATCH 5/9] Updated openssl linux amd64 binary --- openssl | Bin 3527835 -> 3527835 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/openssl b/openssl index 85b13b3d7a5ce9dc9c6ff5c905c03b1bd6f06096..aeb3feb137753308e384dfddc268cf4feaf6a7d2 100755 GIT binary patch delta 545 zcmXxePe>GT6bJBmqrNqDcQwt_{;bxT|C@64;K7!O+BV&_X(gE@@}NO1b;yHD94ET-=UJ^1k6_np2Ag&kUe^|W5M=b7Jp zlwMx#ew}qD^bbqM!1lMdc|Dug35vS}N}){Ub)FAXLWSwj}jPg9>rzJEklQ9+w$ytoIC?{Xz{EAHJ9pRKvEoX}ti`P~C+imPP`kW)%qR3y-Xu6l zE%H6V2WYV|%DY6L)Z!EN5>3hK7`FxE)tYwv!yia%#W9_pV&43pqnRz!vDN7qM~P-6 zY;!-|k!hRnQ$aRt4%4KZ**r%Bl1rk%v+P=u!2{)R4l1A$s=y0A@Iy7!zJI=BG! za1k1y5t`r Womneoz%959y%2&vWu^LuU;YIVh{#|7 delta 541 zcmXxeO-R#m7zgnG^Vgnf<$u1dH>*{?<;?!2?UL47<(M#SY8zXE3ttG)TZnY1%QlvZ zbqSu43f)RPCNV@ zvc}&0$>n^LTfY|ii?sMVcWOxOcX1g}LJU7&qYm-q*QiYSxt5;GvY)f$kf+_eLto`l z4~MB*zVz@GTBLjV2hnRaAK((Ahq4&t`kFhXn)Z9cYL998ai`3D{`5b4;~97Ap4ttv zmnbT(5IgCHjEA_L79|~G7Y)l^h-au(Cc`KYNi2mKEKmf+a1ai`VJHCutWXMNP!1JP z2{t$aN8uP8hZ9f*C*c&F20NUAvv3Zop$2N94(j1NG=Kvd;Q} Date: Wed, 1 Apr 2015 12:50:01 -0400 Subject: [PATCH 6/9] Re-add curve fallback detection --- cipherscan | 114 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 20 deletions(-) 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 From b2a399617f3ecefc239a467861895c2464ef2c41 Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Wed, 1 Apr 2015 14:50:49 -0400 Subject: [PATCH 7/9] Use new JSON format in parse_results --- top1m/parse_results.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/top1m/parse_results.py b/top1m/parse_results.py index 8e69005..fa9fb85 100644 --- a/top1m/parse_results.py +++ b/top1m/parse_results.py @@ -147,16 +147,9 @@ for r,d,flist in os.walk(path): if len(results['ciphersuite']) < 1: continue - """ save ECC curve stats """ - if 'curve_fallback' in results: - tempeccfallback = results['curve_fallback'] - if 'curve_ordering' in results: - tempeccordering = results['curve_ordering'] - if 'curve' in results: - for curve in results['curve']: - tempecccurve[curve] = 1 - if len(results['curve']) == 1: - tempecccurve[curve + ' Only'] = 1 + """ save ECC fallback """ + if 'curves_fallback' in results: + tempeccfallback = results['curves_fallback'] """ loop over list of ciphers """ for entry in results['ciphersuite']: @@ -282,6 +275,16 @@ for r,d,flist in os.walk(path): TLS1_1 = True elif protocol == 'TLSv1.2': TLS1_2 = True + + """ save ECC curves stats """ + if 'curves_ordering' in entry: + tempeccordering = entry['curves_ordering'] + if 'curves' in entry: + for curve in entry['curves']: + tempecccurve[curve] = 1 + if len(entry['curves']) == 1: + tempecccurve[curve + ' Only'] = 1 + json_file.close() """ don't store stats from unusued servers """ From a966574edc1feef8733422d72d8c9013def0ade5 Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Wed, 1 Apr 2015 14:51:01 -0400 Subject: [PATCH 8/9] Fix curve fallback detection --- cipherscan | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/cipherscan b/cipherscan index 7804f13..3948edc 100755 --- a/cipherscan +++ b/cipherscan @@ -840,10 +840,8 @@ test_curves() { 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 @@ -858,7 +856,12 @@ test_curves_fallback() { 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" + fallback_supported="unknown" + + if [ "$ecc_ciphers" == "" ]; then + verbose "No ECC cipher found, can't test curve fallback" + return + fi # prepare the ssl command we'll be using local sslcommand="" @@ -885,20 +888,16 @@ test_curves_fallback() { IFS=':' local test_curves="${curves[*]}" IFS="$OLDIFS" - verbose "Testing $test_curves" + verbose "Testing $sslcommand -curves $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 + verbose "Curve fallback failed, server refused connection" + fallback_supported="False" + break else # server accepted connection local ephem_data=(${current_pfs//,/ }) From 4a6ff56b8155877d17127e1f24f7fe43729a6bea Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Thu, 2 Apr 2015 04:39:59 -0400 Subject: [PATCH 9/9] Add back support for old curve json format in parse results --- top1m/parse_results.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/top1m/parse_results.py b/top1m/parse_results.py index fa9fb85..6c5326a 100644 --- a/top1m/parse_results.py +++ b/top1m/parse_results.py @@ -147,10 +147,21 @@ for r,d,flist in os.walk(path): if len(results['ciphersuite']) < 1: continue - """ save ECC fallback """ + """ save ECC fallback (new format) """ if 'curves_fallback' in results: tempeccfallback = results['curves_fallback'] + """ save ECC curve stats (old format) """ + if 'curve_fallback' in results: + tempeccfallback = results['curve_fallback'] + if 'curve_ordering' in results: + tempeccordering = results['curve_ordering'] + if 'curve' in results: + for curve in results['curve']: + tempecccurve[curve] = 1 + if len(results['curve']) == 1: + tempecccurve[curve + ' Only'] = 1 + """ loop over list of ciphers """ for entry in results['ciphersuite']: