From cc014f085d918b617aaa13238906f1ac3b49f980 Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Fri, 27 Mar 2015 19:03:27 -0400 Subject: [PATCH] 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))"