2
0
mirror of https://github.com/mozilla/cipherscan.git synced 2024-11-16 20:03:41 +01:00

add test for TLSv1.2 PFS key exchange

since the signature and hash algorithm in TLSv1.2 is selectable by server
and negotiated using TLS extensions, we can check what sig algs is
the server willing to perform and whatever it does honour client
selection

it also tests what happens if the client doesn't offer any sigalgs that
are necessary to use the ciphers selected by server
This commit is contained in:
Hubert Kario 2014-11-01 20:26:31 +01:00
parent 460f9cf1f6
commit 434b383f01
3 changed files with 367 additions and 4 deletions

View File

@ -4,7 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Authors: Julien Vehent [:ulfr] - 201{3,4} # Authors: Julien Vehent [:ulfr] - 201{3,4}
# Hubert Kario - 2014 # Hubert Kario - 2014, 2015
# vim: autoindent tabstop=4 shiftwidth=4 expandtab softtabstop=4 filetype=sh # vim: autoindent tabstop=4 shiftwidth=4 expandtab softtabstop=4 filetype=sh
@ -207,12 +207,18 @@ TEST_TOLERANCE="True"
SNI="True" SNI="True"
# openssl formated list of curves that will cause server to select ECC suite # openssl formated list of curves that will cause server to select ECC suite
ecc_ciphers="" ecc_ciphers=""
TEST_KEX_SIGALG="False"
unset known_certs unset known_certs
declare -A known_certs declare -A known_certs
unset cert_checksums unset cert_checksums
declare -A cert_checksums declare -A cert_checksums
# array with results of tolerance scans (TLS version, extensions, etc.) # array with results of tolerance scans (TLS version, extensions, etc.)
declare -A tls_tolerance declare -A tls_tolerance
# array with info on type of fallback on unknown sigalgs (or required ones)
declare -A sigalgs_fallback
# array with preferred sigalgs for aRSA and aECDSA ciphers
declare -a sigalgs_preferred_rsa
declare -a sigalgs_preferred_ecdsa
# because running external commands like sleep incurs a fork penalty, we # because running external commands like sleep incurs a fork penalty, we
# first check if it is necessary # first check if it is necessary
@ -248,6 +254,8 @@ Use one of the options below:
-o | --openssl path/to/your/openssl binary you want to use. -o | --openssl path/to/your/openssl binary you want to use.
--savecrt path where to save untrusted and leaf certificates --savecrt path where to save untrusted and leaf certificates
--[no-]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)
--sigalg test signature algorithms used in TLSv1.2 ephemeral ciphers
(req. OpenSSL 1.0.2)
--[no-]tolerance test TLS tolerance --[no-]tolerance test TLS tolerance
--no-sni don't use Server Name Indication --no-sni don't use Server Name Indication
--no-colors don't use terminal colors --no-colors don't use terminal colors
@ -367,6 +375,7 @@ parse_openssl_output() {
# clear variables in case matching doesn't hit them # clear variables in case matching doesn't hit them
current_ocspstaple="False" current_ocspstaple="False"
current_cipher="" current_cipher=""
current_kex_sigalg=""
current_pfs="" current_pfs=""
current_protocol="" current_protocol=""
current_tickethint="None" current_tickethint="None"
@ -402,6 +411,13 @@ parse_openssl_output() {
continue continue
fi fi
# extract the signing algorithm used in TLSv1.2 ephemeral kex
if [[ $line =~ Peer\ signing\ digest ]]; then
local match=($line)
current_kex_sigalg="${match[3]}"
continue
fi
# extract data about selected temporary key # extract data about selected temporary key
if [[ $line =~ Server\ Temp\ Key ]]; then if [[ $line =~ Server\ Temp\ Key ]]; then
local match=($line) local match=($line)
@ -679,6 +695,21 @@ get_cipher_pref() {
fi fi
} }
display_sigalgs_in_terminal() {
(echo "prio sigalg"
for sigalg in "$@"; do
if [[ $sigalg == "MD5" ]]; then
color="${c_red}"
elif [[ $sigalg == "SHA1" ]]; then
color="${c_yellow}"
else
color="${c_green}"
fi
echo -e "$cnt ${color}$sigalg${c_reset}"
cnt=$((cnt+1))
done )| column -t
}
display_results_in_terminal() { display_results_in_terminal() {
# Display the results # Display the results
ctr=1 ctr=1
@ -693,13 +724,15 @@ display_results_in_terminal() {
if [[ $USECOLORS == "True" && -x /usr/bin/tput ]] && tput setaf 1 >&/dev/null; then if [[ $USECOLORS == "True" && -x /usr/bin/tput ]] && tput setaf 1 >&/dev/null; then
c_blue="\033[0;34m" c_blue="\033[0;34m"
c_green="\033[0;32m" c_green="\033[0;32m"
c_yellow="\033[0;33m"
c_red="\033[0;31m" c_red="\033[0;31m"
c_reset="\033[0m" c_reset="\033[0m"
else else
c_reset=
c_blue= c_blue=
c_green= c_green=
c_yellow=
c_red= c_red=
c_reset=
fi fi
echo "Target: $TARGET"; echo echo "Target: $TARGET"; echo
@ -824,7 +857,49 @@ display_results_in_terminal() {
echo -e "Curves ordering: $curvesordering - fallback: $fallback_supported" echo -e "Curves ordering: $curvesordering - fallback: $fallback_supported"
fi fi
if [[ $TEST_KEX_SIGALG == "True" ]]; then
echo
echo "TLSv1.2 ephemeral sigalgs:"
for auth in "ECDSA" "RSA"; do
# not colored as neither of that results alone is good or bad
if [[ -z ${sigalgs_fallback[$auth]} ]]; then
echo "no PFS $auth ciphers detected"
elif [[ ${sigalgs_fallback[$auth]} == "False" ]]; then
echo "no PFS $auth fallback"
elif [[ ${sigalgs_fallback[$auth]} == "intolerant" ]]; then
echo "$auth test: intolerant of sigalg removal"
elif [[ ${sigalgs_fallback[$auth]} =~ "pfs-" ]]; then
echo "PFS $auth fallbacks to ${sigalgs_fallback[$auth]}"
else
echo "server forces ${sigalgs_fallback[$auth]} with $auth"
fi
done
if [[ ${#sigalgs_preferred_ecdsa[@]} -gt 0 ]]; then
echo
if [[ ${sigalgs_preferred_ecdsa[0]} == "Fail" ]]; then
echo -e "${c_red}ECDSA test failed${c_reset}"
else
local cnt=1
echo "Supported PFS ECDSA signature algorithms"
display_sigalgs_in_terminal "${sigalgs_preferred_ecdsa[@]}"
fi
fi
if [[ ${#sigalgs_preferred_rsa[@]} -gt 0 ]]; then
echo
if [[ ${sigalgs_preferred_rsa[0]} == "Fail" ]]; then
echo -e "${c_red}RSA test failed${c_reset}"
else
local cnt=1
echo "Supported PFS RSA signature algorithms"
display_sigalgs_in_terminal "${sigalgs_preferred_rsa[@]}"
fi
fi
fi
if [[ $TEST_TOLERANCE == "True" ]]; then if [[ $TEST_TOLERANCE == "True" ]]; then
echo
if [[ $tls_tolerance['big-TLSv1.2'] =~ TLSv1.2 ]]; then if [[ $tls_tolerance['big-TLSv1.2'] =~ TLSv1.2 ]]; then
echo -e "TLS Tolerance: ${c_green}yes${c_reset}" echo -e "TLS Tolerance: ${c_green}yes${c_reset}"
else else
@ -877,6 +952,40 @@ display_results_in_json() {
if [[ $TEST_CURVES == "True" ]]; then if [[ $TEST_CURVES == "True" ]]; then
echo -n ",\"curves_fallback\":\"$fallback_supported\"" echo -n ",\"curves_fallback\":\"$fallback_supported\""
fi fi
if [[ $TEST_KEX_SIGALG == "True" ]]; then
echo -n ',"sigalgs":{'
echo -n "\"ECDSA-fallback\":\"${sigalgs_fallback[ECDSA]}\","
echo -n "\"RSA-fallback\":\"${sigalgs_fallback[RSA]}\""
if [[ ${#sigalgs_preferred_ecdsa[@]} -gt 0 ]]; then
echo -n ","
echo -n '"ECDSA":['
local cnt=0
for sigalg in "${sigalgs_preferred_ecdsa[@]}"; do
if [[ $cnt -gt 0 ]]; then
echo -n ','
fi
echo -n "\"$sigalg\""
cnt=$((cnt+1))
done
echo -n ']'
fi
if [[ ${#sigalgs_preferred_rsa[@]} -gt 0 ]]; then
echo -n ","
echo -n '"RSA":['
local cnt=0
for sigalg in "${sigalgs_preferred_rsa[@]}"; do
if [[ $cnt -gt 0 ]]; then
echo -n ','
fi
echo -n "\"$sigalg\""
cnt=$((cnt+1))
done
echo -n ']'
fi
echo -n '}'
fi
echo -n ',"configs":{' echo -n ',"configs":{'
ctr=0 ctr=0
for test_name in "${!tls_tolerance[@]}"; do for test_name in "${!tls_tolerance[@]}"; do
@ -1345,6 +1454,214 @@ test_tls_tolerance() {
fi fi
} }
test_kex_sigalgs() {
local ecdsa_sigalgs=("ECDSA+SHA512" "ECDSA+SHA384" "ECDSA+SHA256"
"ECDSA+SHA224" "ECDSA+SHA1" "ECDSA+MD5")
local rsa_sigalgs=("RSA+SHA512" "RSA+SHA384" "RSA+SHA256"
"RSA+SHA224" "RSA+SHA1" "RSA+MD5")
local supported_rsa_ciphers=""
local supported_ecdsa_ciphers=""
local supported_ciphers=()
# check if TLS1.2 is supported by server, as tests needs it
# collect ciphers
local tls12="False"
for cipher in "${cipherspref[@]}"; do
local ciph_data=($cipher)
if [[ ${ciph_data[1]} =~ TLSv1.2 ]]; then
tls12="True"
fi
supported_ciphers+=(${ciph_data[0]})
done
if [[ $tls12 == "False" ]]; then
return
fi
# create cipher list for ecdsa and rsa tests that include non ephemeral
# ciphers for fallback
for cipher in "${supported_ciphers[@]}"; do
if [[ $cipher =~ DHE-ECDSA ]]; then
if [[ $supported_ecdsa_ciphers ]]; then
supported_ecdsa_ciphers+=":"
fi
supported_ecdsa_ciphers+="$cipher"
elif [[ ${supported_ecdsa_ciphers} ]]; then
supported_ecdsa_ciphers+=":$cipher"
fi
if [[ $cipher =~ DHE-RSA ]]; then
if [[ $supported_rsa_ciphers ]]; then
supported_rsa_ciphers+=":"
fi
supported_rsa_ciphers+="$cipher"
elif [[ ${supported_rsa_ciphers} ]]; then
supported_rsa_ciphers+=":$cipher"
fi
done
#
# Test default sigalgs for aECDSA ciphers
#
if [[ $supported_ecdsa_ciphers ]]; then
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $supported_ecdsa_ciphers"
# since some ciphers supported by server may be SSLv2 only, we need to
# force use of TLSv1.2, otherwise openssl will send a SSLv2 compatible
# client hello
sslcommand+=" -no_ssl2 -no_ssl3"
local test_ecdsa_sigalgs=("${ecdsa_sigalgs[@]}")
local test_rsa_sigalgs=("${rsa_sigalgs[@]}")
while true; do
join_array_by_char ":" "${test_ecdsa_sigalgs[@]}" \
"${test_rsa_sigalgs[@]}"
local test_sigalgs="$joined_array"
ratelimit
verbose "Testing default ECDSA sig algs with $sslcommand -sigalgs $test_sigalgs"
local tmp=$(echo Q | $sslcommand -sigalgs $test_sigalgs 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "server selected $current_cipher, $current_protocol, $current_kex_sigalg"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == "0000" ]]; then
if [[ ${#test_ecdsa_sigalgs[@]} -eq 0 ]]; then
sigalgs_fallback["ECDSA"]="False"
else
sigalgs_fallback["ECDSA"]="intolerant"
fi
break
fi
if [[ $current_cipher =~ DHE-ECDSA ]]; then
if [[ -z $current_kex_sigalg ]]; then
# if we didn't get a sigalg that means the test failed
sigalgs_preferred_ecdsa=("Fail")
break
fi
if [[ "${test_ecdsa_sigalgs[*]}" =~ "ECDSA+$current_kex_sigalg" ]]; then
# save sigalg for reporting
sigalgs_preferred_ecdsa+=("$current_kex_sigalg")
# remove it from offered
local id
for id in "${!test_ecdsa_sigalgs[@]}"; do
if [[ ${test_ecdsa_sigalgs[$id]} =~ $current_kex_sigalg ]]; then
unset test_ecdsa_sigalgs[$id]
break
fi
done
# continue testing
else
# server selected sigalg we didn't offer
sigalgs_fallback["ECDSA"]="$current_kex_sigalg"
break
fi
elif [[ $current_cipher =~ DHE-RSA ]]; then
# we got a fallback to a RSA based cipher
if [[ -z $current_kex_sigalg ]]; then
sigalgs_fallback["ECDSA"]="pfs-rsa"
else
sigalgs_fallback["ECDSA"]="pfs-rsa-${current_kex_sigalg}"
fi
break
else
# we got a fallback to a non PFS cipher, that's good too
sigalgs_fallback["ECDSA"]="soft-nopfs"
break
fi
[ "$OUTPUTFORMAT" == "terminal" ] && [ $DEBUG -lt 1 ] && echo -n '.'
done
fi
#
# Test default sigalgs for aRSA ciphers
#
if [[ ${supported_rsa_ciphers} ]]; then
local sslcommand="$TIMEOUTBIN $TIMEOUT $OPENSSLBIN s_client"
if [ -n "$CAPATH" ]; then
sslcommand+=" -CApath $CAPATH -showcerts"
elif [ -e "$CACERTS" ]; then
sslcommand+=" -CAfile $CACERTS"
fi
sslcommand+=" $SCLIENTARGS -connect $TARGET -cipher $supported_rsa_ciphers"
# since some ciphers supported by server may be SSLv2 only, we need to
# force use of TLSv1.2, otherwise openssl will send a SSLv2 compatible
# client hello
sslcommand+=" -no_ssl2 -no_ssl3"
local test_ecdsa_sigalgs=("${ecdsa_sigalgs[@]}")
local test_rsa_sigalgs=("${rsa_sigalgs[@]}")
while true; do
join_array_by_char ":" "${test_rsa_sigalgs[@]}" \
"${test_ecdsa_sigalgs[@]}"
local test_sigalgs="$joined_array"
ratelimit
verbose "Testing default RSA sig algs with $sslcommand -sigalgs $test_sigalgs"
local tmp=$(echo Q | $sslcommand -sigalgs $test_sigalgs 2>/dev/null)
parse_openssl_output <<<"$tmp"
verbose "server selected $current_cipher, $current_protocol, $current_kex_sigalg"
if [[ -z $current_protocol || $current_cipher == "(NONE)" \
|| $current_cipher == "0000" ]]; then
if [[ ${#test_rsa_sigalgs[@]} -eq 0 ]]; then
sigalgs_fallback["RSA"]="False"
else
sigalgs_fallback["RSA"]="intolerant"
fi
break
fi
if [[ $current_cipher =~ DHE-RSA ]]; then
if [[ -z $current_kex_sigalg ]]; then
# if we didn't get a sigalg that means the test failed
sigalgs_preferred_rsa=("Fail")
break
fi
if [[ "${test_rsa_sigalgs[*]}" =~ "RSA+$current_kex_sigalg" ]]; then
# save sigalg for reporting
sigalgs_preferred_rsa+=("$current_kex_sigalg")
# remove it from offered
local id
for id in "${!test_rsa_sigalgs[@]}"; do
if [[ ${test_rsa_sigalgs[$id]} =~ $current_kex_sigalg ]]; then
unset test_rsa_sigalgs[$id]
break
fi
done
# continue testing
else
# server selected sigalg we didn't offer
sigalgs_fallback["RSA"]="$current_kex_sigalg"
break
fi
elif [[ $current_cipher =~ DHE-ECDSA ]]; then
# we got a fallback to an ECDSA based cipher
if [[ -z $current_kex_sigalg ]]; then
sigalgs_fallback["RSA"]="pfs-ecdsa"
else
sigalgs_fallback["RSA"]="pfs-ecdsa-${current_kex_sigalg}"
fi
break
else
# we got a fallback to a non PFS cipher, that's good too
sigalgs_fallback["RSA"]="soft-nopfs"
break
fi
[ "$OUTPUTFORMAT" == "terminal" ] && [ $DEBUG -lt 1 ] && echo -n '.'
done
fi
}
# If no options are given, give usage information and exit (with error code) # If no options are given, give usage information and exit (with error code)
if (( $# == 0 )); then if (( $# == 0 )); then
usage usage
@ -1414,6 +1731,10 @@ do
TEST_CURVES="False" TEST_CURVES="False"
shift 1 shift 1
;; ;;
--sigalg)
TEST_KEX_SIGALG="True"
shift 1
;;
--tolerance) --tolerance)
TEST_TOLERANCE="True" TEST_TOLERANCE="True"
shift 1 shift 1
@ -1567,6 +1888,10 @@ fi
test_serverside_ordering test_serverside_ordering
if [[ $TEST_KEX_SIGALG == "True" ]]; then
test_kex_sigalgs
fi
if [[ $TEST_CURVES == "True" ]]; then if [[ $TEST_CURVES == "True" ]]; then
test_curves_fallback test_curves_fallback
fi fi

View File

@ -135,6 +135,8 @@ fallback_ids['v2-big-TLSv1.2'] = i
i+=1 i+=1
# 3rd padding space # 3rd padding space
fallback_ids[' '] = i fallback_ids[' '] = i
pfssigalgfallback = defaultdict(int)
pfssigalgs = defaultdict(int)
dsarsastack = 0 dsarsastack = 0
total = 0 total = 0
for r,d,flist in os.walk(path): for r,d,flist in os.walk(path):
@ -155,6 +157,8 @@ for r,d,flist in os.walk(path):
tempfallbacks = {} tempfallbacks = {}
""" supported ciphers by the server under scan """ """ supported ciphers by the server under scan """
tempcipherstats = {} tempcipherstats = {}
temppfssigalgfallback = {}
temppfssigalgs = {}
ciphertypes = 0 ciphertypes = 0
AESGCM = False AESGCM = False
AESCBC = False AESCBC = False
@ -249,6 +253,23 @@ for r,d,flist in os.walk(path):
if len(results['curve']) == 1: if len(results['curve']) == 1:
tempecccurve[curve + ' Only'] = 1 tempecccurve[curve + ' Only'] = 1
""" collect TLSv1.2 PFS ciphersuite sigalgs """
if 'sigalgs' in results:
if results['sigalgs']['ECDSA-fallback']:
temppfssigalgfallback['ECDSA ' + results['sigalgs']['ECDSA-fallback']] = 1
if results['sigalgs']['RSA-fallback']:
temppfssigalgfallback['RSA ' + results['sigalgs']['RSA-fallback']] = 1
if 'RSA' in results['sigalgs'] and results['sigalgs']['RSA'][0] != 'Fail':
for pfssigalg in results['sigalgs']['RSA']:
temppfssigalgs['RSA-' + pfssigalg]=1
if len(results['sigalgs']['RSA']) == 1:
temppfssigalgs['RSA-' + results['sigalgs']['RSA'][0] + ' Only'] = 1
if 'ECDSA' in results['sigalgs'] and results['sigalgs']['ECDSA'][0] != 'Fail':
for pfssigalg in results['sigalgs']['ECDSA']:
temppfssigalgs['ECDSA-' + pfssigalg]=1
if len(results['sigalgs']['ECDSA']) == 1:
temppfssigalgs['ECDSA-' + results['sigalgs']['ECDSA'][0] + ' Only'] = 1
if 'configs' in results: if 'configs' in results:
tolerance = [' '] * len(fallback_ids) tolerance = [' '] * len(fallback_ids)
for entry in results['configs']: for entry in results['configs']:
@ -531,6 +552,11 @@ for r,d,flist in os.walk(path):
else: else:
ocspstaple['Unsupported'] += 1 ocspstaple['Unsupported'] += 1
for s in temppfssigalgfallback:
pfssigalgfallback[s] += 1
for s in temppfssigalgs:
pfssigalgs[s] += 1
""" store cipher stats """ """ store cipher stats """
if AESGCM: if AESGCM:
cipherstats['AES-GCM'] += 1 cipherstats['AES-GCM'] += 1
@ -735,6 +761,18 @@ for stat in sorted(eccordering):
percent = round(eccordering[stat] / total * 100, 4) percent = round(eccordering[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(25) + " " + str(eccordering[stat]).ljust(10) + str(percent).ljust(9) + "\n") sys.stdout.write(stat.ljust(25) + " " + str(eccordering[stat]).ljust(10) + str(percent).ljust(9) + "\n")
print("\nTLSv1.2 PFS supported sigalgs Count Percent ")
print("------------------------------+---------+--------")
for stat in sorted(pfssigalgs):
percent = round(pfssigalgs[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(pfssigalgs[stat]).ljust(10) + str(percent).ljust(9) + "\n")
print("\nTLSv1.2 PFS sigalg fallback Count Percent ")
print("------------------------------+---------+--------")
for stat in sorted(pfssigalgfallback):
percent = round(pfssigalgfallback[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(pfssigalgfallback[stat]).ljust(10) + str(percent).ljust(9) + "\n")
print("\nTLS session ticket hint Count Percent ") print("\nTLS session ticket hint Count Percent ")
print("-------------------------+---------+--------") print("-------------------------+---------+--------")
for stat in natural_sort(tickethint): for stat in natural_sort(tickethint):

View File

@ -68,7 +68,7 @@ function scan_host() {
if [ $? -gt 0 ]; then if [ $? -gt 0 ]; then
return return
fi fi
../cipherscan --capath ca_files --saveca --curves --savecrt certs --delay 2 -json -servername $1 $2:443 > results/$1@$2 ../cipherscan --capath ca_files --saveca --curves --savecrt certs --delay 2 --sigalg -json -servername $1 $2:443 > results/$1@$2
} }
function scan_host_no_sni() { function scan_host_no_sni() {
@ -80,7 +80,7 @@ function scan_host_no_sni() {
if [ $? -gt 0 ]; then if [ $? -gt 0 ]; then
return return
fi fi
../cipherscan --capath ca_files --saveca --curves --savecrt certs --delay 2 -json $1:443 > results/$1 ../cipherscan --capath ca_files --saveca --curves --savecrt certs --delay 2 --sigalg -json $1:443 > results/$1
} }
function scan_hostname() { function scan_hostname() {