mirror of
https://github.com/mozilla/cipherscan.git
synced 2024-12-25 20:23:41 +01:00
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.
This commit is contained in:
parent
800eff19ce
commit
c52e008347
280
cipherscan
280
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
|
||||
|
@ -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):
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user