make cipher selection simulation generic

it's relatively easy to make the cipher selection generic,
so that adding different clients is as easy as converting their
client hello cipher ordering to openssl cipher names
This commit is contained in:
Hubert Kario 2014-10-12 20:24:56 +02:00
parent c82bc44558
commit 76d791fcbe
1 changed files with 111 additions and 81 deletions

View File

@ -21,8 +21,10 @@ def natural_sort(l):
alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
return sorted(l, key = alphanum_key) return sorted(l, key = alphanum_key)
""" list of ciphers offerred by Firefox 29 by default """ """ client config cipher simulation """
firefox_ciphers=[ client_ciphers={}
""" list of ciphers offered by Firefox 29 by default """
client_ciphers['FF 29']=[
'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA',
@ -50,10 +52,19 @@ firefox_ciphers=[
report_untrused=False report_untrused=False
cipherstats = defaultdict(int) cipherstats = defaultdict(int)
FF_RC4_Only_cipherstats = defaultdict(int)
FF_RC4_preferred_cipherstats = defaultdict(int) # stats about different client performance
FF_incompatible_cipherstats = defaultdict(int) # ciphers selected by them, unsupported, etc.
FF_selected_cipherstats = defaultdict(int) client_RC4_Only_cipherstats={}
client_RC4_preferred_cipherstats={}
client_incompatible_cipherstats={}
client_selected_cipherstats={}
for client_name in client_ciphers:
client_RC4_Only_cipherstats[client_name] = defaultdict(int)
client_RC4_preferred_cipherstats[client_name] = defaultdict(int)
client_incompatible_cipherstats[client_name] = defaultdict(int)
client_selected_cipherstats[client_name] = defaultdict(int)
cipherordering = defaultdict(int) cipherordering = defaultdict(int)
pfsstats = defaultdict(int) pfsstats = defaultdict(int)
protocolstats = defaultdict(int) protocolstats = defaultdict(int)
@ -84,13 +95,22 @@ for r,d,flist in os.walk(path):
DES3 = False DES3 = False
CAMELLIA = False CAMELLIA = False
RC4 = False RC4 = False
""" the following depends on FF_compat, so by default it can be True """ """ variables to support handshake simulation for different clients """
RC4_Only_FF = True client_RC4_Only={}
FF_compat = False client_compat={}
temp_FF_incompat = {} temp_client_incompat={}
client_RC4_Pref={}
client_selected={}
for client_name in client_ciphers:
# the following depends on client_compat, so by default it can be True
client_RC4_Only[client_name]=True
client_compat[client_name]=False
temp_client_incompat[client_name]={}
client_RC4_Pref[client_name]=None
client_selected[client_name]=None
""" server side list of supported ciphers """
list_of_ciphers = [] list_of_ciphers = []
FF_RC4_Pref = None
FF_selected = None
ADH = False ADH = False
DHE = False DHE = False
AECDH = False AECDH = False
@ -133,15 +153,17 @@ for r,d,flist in os.walk(path):
list_of_ciphers.append(entry['cipher']) list_of_ciphers.append(entry['cipher'])
# check if the advertised ciphers are not effectively RC4 Only # check if the advertised ciphers are not effectively RC4 Only
# for firefox or incompatible with firefox # for clients or incompatible with them
if entry['cipher'] in firefox_ciphers: for client_name in client_ciphers:
# if this is first cipher and we already are getting RC4 if entry['cipher'] in client_ciphers[client_name]:
# then it means that RC4 is preferred # if this is first cipher and we already are getting RC4
FF_compat = True # then it means that RC4 is preferred (and client is
if not 'RC4' in entry['cipher']: # compatible with server)
RC4_Only_FF = False client_compat[client_name]=True
else: if not 'RC4' in entry['cipher']:
temp_FF_incompat[entry['cipher']] = 1 client_RC4_Only[client_name] = False
else:
temp_client_incompat[client_name][entry['cipher']] = 1
""" store the ciphers supported """ """ store the ciphers supported """
if 'ADH' in entry['cipher'] or 'AECDH' in entry['cipher']: if 'ADH' in entry['cipher'] or 'AECDH' in entry['cipher']:
@ -279,22 +301,23 @@ for r,d,flist in os.walk(path):
else: else:
cipherordering['Unknown'] += 1 cipherordering['Unknown'] += 1
""" simulate handshake with Firefox """ """ simulate handshake with clients """
if FF_compat: for client_name in client_ciphers:
if 'serverside' in results and results['serverside'] == "False": if client_compat[client_name]:
for cipher in firefox_ciphers: if 'serverside' in results and results['serverside'] == "False":
if cipher in list_of_ciphers: for cipher in client_ciphers[client_name]:
FF_selected = cipher if cipher in list_of_ciphers:
if 'RC4' in cipher: client_selected[client_name] = cipher
FF_RC4_Pref = True if 'RC4' in cipher:
break client_RC4_Pref[client_name] = True
else: break
for cipher in list_of_ciphers: else:
if cipher in firefox_ciphers: for cipher in list_of_ciphers:
FF_selected = cipher if cipher in client_ciphers[client_name]:
if 'RC4' in cipher: client_selected[client_name] = cipher
FF_RC4_Pref = True if 'RC4' in cipher:
break client_RC4_Pref[client_name] = True
break
for s in tempsigstats: for s in tempsigstats:
sigalg[s] += 1 sigalg[s] += 1
@ -346,26 +369,29 @@ for r,d,flist in os.walk(path):
cipherstats['RC4 forced in TLS1.1+'] += 1 cipherstats['RC4 forced in TLS1.1+'] += 1
cipherstats['RC4 Preferred'] += 1 cipherstats['RC4 Preferred'] += 1
if FF_compat: for client_name in client_ciphers:
if 'ECDHE' in FF_selected: if client_compat[client_name]:
FF_selected_cipherstats['x:ECDHE'] += 1 if 'ECDHE' in client_selected[client_name]:
elif 'DHE' in FF_selected or 'EDH' in FF_selected: client_selected_cipherstats[client_name]['x:ECDHE'] += 1
FF_selected_cipherstats['x:DHE'] += 1 elif 'DHE' in client_selected[client_name]:
client_selected_cipherstats[client_name]['x:DHE'] += 1
else:
client_selected_cipherstats[client_name]['x:kRSA'] += 1
client_selected_cipherstats[client_name][client_selected[client_name]] += 1
if client_RC4_Only[client_name] and ciphertypes != 1:
cipherstats['x:' + client_name + ' RC4 Only'] += 1
for cipher in temp_client_incompat[client_name]:
client_RC4_Only_cipherstats[client_name][cipher] += 1
if client_RC4_Pref[client_name] and not 'RC4' in results['ciphersuite'][0]['cipher']:
cipherstats['x:' + client_name + ' RC4 Preferred'] += 1
for cipher in temp_client_incompat[client_name]:
client_RC4_preferred_cipherstats[client_name][cipher] += 1
else: else:
FF_selected_cipherstats['x:kRSA'] += 1 cipherstats['x:' + client_name + ' incompatible'] += 1
FF_selected_cipherstats[FF_selected] += 1 for cipher in temp_client_incompat[client_name]:
if RC4_Only_FF and ciphertypes != 1: client_incompatible_cipherstats[client_name][cipher] += 1
cipherstats['x:FF 29 RC4 Only'] += 1
for cipher in temp_FF_incompat:
FF_RC4_Only_cipherstats[cipher] += 1
if FF_RC4_Pref and not 'RC4' in results['ciphersuite'][0]['cipher']:
cipherstats['x:FF 29 RC4 Preferred'] += 1
for cipher in temp_FF_incompat:
FF_RC4_preferred_cipherstats[cipher] += 1
else:
cipherstats['x:FF 29 incompatible'] += 1
for cipher in temp_FF_incompat:
FF_incompatible_cipherstats[cipher] += 1
for cipher in tempcipherstats: for cipher in tempcipherstats:
cipherstats[cipher] += 1 cipherstats[cipher] += 1
@ -420,12 +446,13 @@ for r,d,flist in os.walk(path):
#if total % 1999 == 0: #if total % 1999 == 0:
# break # break
""" The 'x:FF 29 RC4 Preferred' counts only sites that effectively prefer """ The 'x:' + client_name + ' RC4 Preferred' counts only sites that
RC4 when using FF, to make reporting more readable, sum it with sites effectively prefer RC4 when using given client, to make reporting more
that do that for all ciphers""" readable, sum it with sites that do that for all ciphers"""
if "x:FF 29 RC4 Preferred" in cipherstats and "RC4 Preferred" in cipherstats: for client_name in client_ciphers:
cipherstats['x:FF 29 RC4 Preferred'] += cipherstats['RC4 Preferred'] if 'x:' + client_name + ' RC4 Preferred' in cipherstats and 'RC4 Preferred' in cipherstats:
cipherstats['x:' + client_name + ' RC4 Preferred'] += cipherstats['RC4 Preferred']
print("SSL/TLS survey of %i websites from Alexa's top 1 million" % total) print("SSL/TLS survey of %i websites from Alexa's top 1 million" % total)
if report_untrused == False: if report_untrused == False:
@ -445,29 +472,32 @@ for stat in sorted(cipherordering):
percent = round(cipherordering[stat] / total * 100, 4) percent = round(cipherordering[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(25) + " " + str(cipherordering[stat]).ljust(10) + str(percent).ljust(4) + "\n") sys.stdout.write(stat.ljust(25) + " " + str(cipherordering[stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nFF 29 selected ciphers Count Percent") print("\nCLIENT specific statistics\n")
print("-----------------------------+---------+------")
for stat in sorted(FF_selected_cipherstats):
percent = round(FF_selected_cipherstats[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(FF_selected_cipherstats[stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nFF 29 RC4 Only other ciphers Count Percent") for client_name in client_ciphers:
print("-----------------------------+---------+------") print("\n" + client_name + " selected ciphers Count Percent")
for stat in sorted(FF_RC4_Only_cipherstats): print("-----------------------------+---------+------")
percent = round(FF_RC4_Only_cipherstats[stat] / total * 100, 4) for stat in sorted(client_selected_cipherstats[client_name]):
sys.stdout.write(stat.ljust(30) + " " + str(FF_RC4_Only_cipherstats[stat]).ljust(10) + str(percent).ljust(4) + "\n") percent = round(client_selected_cipherstats[client_name][stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(client_selected_cipherstats[client_name][stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nFF 29 RC4 pref other ciphers Count Percent") print("\n" + client_name + " RC4 Only other ciphers Count Percent")
print("-----------------------------+---------+------") print("-----------------------------+---------+------")
for stat in sorted(FF_RC4_preferred_cipherstats): for stat in sorted(client_RC4_Only_cipherstats[client_name]):
percent = round(FF_RC4_preferred_cipherstats[stat] / total * 100, 4) percent = round(client_RC4_Only_cipherstats[client_name][stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(FF_RC4_preferred_cipherstats[stat]).ljust(10) + str(percent).ljust(4) + "\n") sys.stdout.write(stat.ljust(30) + " " + str(client_RC4_Only_cipherstats[client_name][stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nFF 29 incompatible ciphers Count Percent") print("\n" + client_name + " RC4 pref other ciphers Count Percent")
print("-----------------------------+---------+------") print("-----------------------------+---------+------")
for stat in sorted(FF_incompatible_cipherstats): for stat in sorted(client_RC4_preferred_cipherstats[client_name]):
percent = round(FF_incompatible_cipherstats[stat] / total * 100, 4) percent = round(client_RC4_preferred_cipherstats[client_name][stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(FF_incompatible_cipherstats[stat]).ljust(10) + str(percent).ljust(4) + "\n") sys.stdout.write(stat.ljust(30) + " " + str(client_RC4_preferred_cipherstats[client_name][stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\n" + client_name + " incompatible ciphers Count Percent")
print("-----------------------------+---------+------")
for stat in sorted(client_incompatible_cipherstats[client_name]):
percent = round(client_incompatible_cipherstats[client_name][stat] / total * 100, 4)
sys.stdout.write(stat.ljust(30) + " " + str(client_incompatible_cipherstats[client_name][stat]).ljust(10) + str(percent).ljust(4) + "\n")
print("\nSupported Handshakes Count Percent") print("\nSupported Handshakes Count Percent")
print("-------------------------+---------+-------") print("-------------------------+---------+-------")