From db07aaf45f116a7c221931f822715f25bc1484fd Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Mar 2012 16:01:15 -0400 Subject: [PATCH 1/8] Script to generate ciphers.inc by Arturo --- src/common/get_mozilla_ciphers.py | 117 ++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/common/get_mozilla_ciphers.py diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py new file mode 100644 index 0000000000..59f8b59891 --- /dev/null +++ b/src/common/get_mozilla_ciphers.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# Copyright 2011, The Tor Project, Inc +# original version by Arturo Filastò + +# This script parses Firefox and OpenSSL sources, and uses this information +# to generate a ciphers.inc file. + +# Read the cpp file to understand what Ciphers map to what name +fileA = open('security/manager/ssl/src/nsNSSComponent.cpp','r') + +start = None +lines = fileA.readlines() +for i, line in enumerate(lines): + if line.strip().startswith('static CipherPref CipherPrefs'): + # Get the starting boundary of the Cipher Preferences + start = i + + if start and line.strip().startswith('{NULL, 0}'): + # Get the ending boundary of the Cipher Prefs + end = i + break +fileA.close() + +# Parse the lines and put them into a dict +ciphers = {} +for x in lines[start:end]: + line = x.strip() + if line.startswith('{'): + for i, y in enumerate(line): + if y == '}': + parsed = line[1:i] + key, value = parsed.split(',') + ciphers[key.replace("\"","")] = value.strip() + +# Read the JS file to understand what ciphers are enabled +fileB = open('netwerk/base/public/security-prefs.js', 'r') + +enabled_ciphers = {} +for x in fileB.readlines(): + start = None + line = x.strip() + for i, y in enumerate(line): + if y == "(": + start = i + if start and y == ")": + end = i + if start: + inner = line[start+1:end] + key, value = inner.replace("\"","").split(",") + if key.startswith('security.ssl3'): + enabled_ciphers[key.strip()] = value.strip() +fileB.close() + +used_ciphers = [] +for k, v in enabled_ciphers.items(): + if v == "true": + used_ciphers.append(ciphers[k]) + + +oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h', + '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h', + '/usr/include/openssl/tls1.h') + +sslProto = open('security/nss/lib/ssl/sslproto.h', 'r') +sslProtoD = {} + +# This reads the HEX code for the ciphers that are used +# by firefox. +for x in sslProto.readlines(): + line = x.strip() + if line.lower().startswith("#define"): + # If I ever see somebody putting mixed tab + # and spaces I will cut their fingers :) + line = line.replace('\t', ' ') + key = line.split(' ')[1] + value = line.split(' ')[-1] + key = key.strip() + value = value.strip() + print "%s %s\n\n" % (key, value) + sslProtoD[key] = value +sslProto.close() + +cipher_codes = [] +for x in used_ciphers: + cipher_codes.append(sslProtoD[x].lower()) + +cipher_hex = {} +for fl in oSSLinclude: + fp = open(fl, 'r') + for x in fp.readlines(): + line = x.strip() + if line.lower().startswith("#define"): + line = line.replace('\t', ' ') + value = line.split(' ')[1] + key = line.split(' ')[-1] + key = key.strip() + value = value.strip() + if key.startswith('0x'): + key = key.replace('0x0300','0x').lower() + #print "%s %s" % (key, value) + cipher_hex[key] = value + fp.close() + +for x in cipher_codes: + try: + res = """#ifdef %s + CIPHER(%s, %s) + #else + XCIPHER(%s, %s) + #endif""" % (cipher_hex[x], x, cipher_hex[x], x, cipher_hex[x]) + print res + except: + print "Not found %s" % x + + +#print enabled_ciphers +#print ciphers From a6a905cc11da1f13db08e43b17fb32ed5295a23e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Mar 2012 16:45:38 -0400 Subject: [PATCH 2/8] Make get_mozilla_ciphers script a little more regexy and readable --- src/common/get_mozilla_ciphers.py | 121 +++++++++++++++--------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py index 59f8b59891..378ce01e83 100644 --- a/src/common/get_mozilla_ciphers.py +++ b/src/common/get_mozilla_ciphers.py @@ -1,54 +1,64 @@ #!/usr/bin/python +# coding=utf-8 # Copyright 2011, The Tor Project, Inc -# original version by Arturo Filastò +# original version by Arturo Filastò # This script parses Firefox and OpenSSL sources, and uses this information # to generate a ciphers.inc file. -# Read the cpp file to understand what Ciphers map to what name +import re + +##### +# Read the cpp file to understand what Ciphers map to what name : +# Make "ciphers" a map from name used in the javascript to a cipher macro name fileA = open('security/manager/ssl/src/nsNSSComponent.cpp','r') -start = None -lines = fileA.readlines() -for i, line in enumerate(lines): - if line.strip().startswith('static CipherPref CipherPrefs'): - # Get the starting boundary of the Cipher Preferences - start = i +# The input format is a file containing exactly one section of the form: +# static CipherPref CipherPrefs[] = { +# {"name", MACRO_NAME}, // comment +# ... +# {NULL, 0} +# } - if start and line.strip().startswith('{NULL, 0}'): - # Get the ending boundary of the Cipher Prefs - end = i - break +inCipherSection = False +cipherLines = [] +for line in fileA: + if line.startswith('static CipherPref CipherPrefs'): + # Get the starting boundary of the Cipher Preferences + inCipherSection = True + elif inCipherSection: + line = line.strip() + if line.startswith('{NULL, 0}'): + # At the ending boundary of the Cipher Prefs + break + else: + cipherLines.append(line) fileA.close() # Parse the lines and put them into a dict ciphers = {} -for x in lines[start:end]: - line = x.strip() - if line.startswith('{'): - for i, y in enumerate(line): - if y == '}': - parsed = line[1:i] - key, value = parsed.split(',') - ciphers[key.replace("\"","")] = value.strip() +for line in cipherLines: + m = re.search(r'^{\s*\"([^\"]+)\",\s*(\S*)\s*}', line) + if m: + key,value = m.groups() + ciphers[key] = value -# Read the JS file to understand what ciphers are enabled +##### +# Read the JS file to understand what ciphers are enabled. The format is +# pref("name", true/false); +# Build a map enabled_ciphers from javascript name to "true" or "false", +# and an (unordered!) list of the macro names for those ciphers that are +# enabled. fileB = open('netwerk/base/public/security-prefs.js', 'r') enabled_ciphers = {} -for x in fileB.readlines(): - start = None - line = x.strip() - for i, y in enumerate(line): - if y == "(": - start = i - if start and y == ")": - end = i - if start: - inner = line[start+1:end] - key, value = inner.replace("\"","").split(",") - if key.startswith('security.ssl3'): - enabled_ciphers[key.strip()] = value.strip() +for line in fileB: + m = re.match(r'pref\(\"([^\"]+)\"\s*,\s*(\S*)\s*\)', line) + if not m: + continue + key, val = m.groups() + if key.startswith("security.ssl3"): + enabled_ciphers[key] = val fileB.close() used_ciphers = [] @@ -56,27 +66,21 @@ for k, v in enabled_ciphers.items(): if v == "true": used_ciphers.append(ciphers[k]) - oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h', '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h', '/usr/include/openssl/tls1.h') +##### +# This reads the hex code for the ciphers that are used by firefox. +# sslProtoD is set to a map from macro name to macro value in sslproto.h; +# cipher_codes is set to an (unordered!) list of these hex values. sslProto = open('security/nss/lib/ssl/sslproto.h', 'r') sslProtoD = {} -# This reads the HEX code for the ciphers that are used -# by firefox. -for x in sslProto.readlines(): - line = x.strip() - if line.lower().startswith("#define"): - # If I ever see somebody putting mixed tab - # and spaces I will cut their fingers :) - line = line.replace('\t', ' ') - key = line.split(' ')[1] - value = line.split(' ')[-1] - key = key.strip() - value = value.strip() - print "%s %s\n\n" % (key, value) +for line in sslProto: + m = re.match('#define\s+(\S+)\s+(\S+)', line) + if m: + key, value = m.groups() sslProtoD[key] = value sslProto.close() @@ -84,23 +88,23 @@ cipher_codes = [] for x in used_ciphers: cipher_codes.append(sslProtoD[x].lower()) +#### +# Now read through all the openssl include files, and try to find the openssl +# macro names for those files. cipher_hex = {} for fl in oSSLinclude: fp = open(fl, 'r') - for x in fp.readlines(): - line = x.strip() - if line.lower().startswith("#define"): - line = line.replace('\t', ' ') - value = line.split(' ')[1] - key = line.split(' ')[-1] - key = key.strip() - value = value.strip() + for line in fp.readlines(): + m = re.match('#define\s+(\S+)\s+(\S+)', line) + if m: + value,key = m.groups() if key.startswith('0x'): key = key.replace('0x0300','0x').lower() #print "%s %s" % (key, value) cipher_hex[key] = value fp.close() +# Now generate the output. for x in cipher_codes: try: res = """#ifdef %s @@ -109,9 +113,6 @@ for x in cipher_codes: XCIPHER(%s, %s) #endif""" % (cipher_hex[x], x, cipher_hex[x], x, cipher_hex[x]) print res - except: + except KeyError: print "Not found %s" % x - -#print enabled_ciphers -#print ciphers From 092b9aca8cb8a63022d47afc38b385957f1a59fb Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Mar 2012 16:53:39 -0400 Subject: [PATCH 3/8] Have get_mozilla_ciphers take sourcedirs as arguments --- src/common/get_mozilla_ciphers.py | 34 ++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py index 378ce01e83..9b8af2e0ad 100644 --- a/src/common/get_mozilla_ciphers.py +++ b/src/common/get_mozilla_ciphers.py @@ -5,13 +5,30 @@ # This script parses Firefox and OpenSSL sources, and uses this information # to generate a ciphers.inc file. +# +# It takes two arguments: the location of a firefox source directory, and the +# location of an openssl source directory. +import os import re +import sys + +if len(sys.argv) != 3: + print >>sys.stderr, "Syntax: get_mozilla_ciphers.py " + sys.exit(1) + +ff_root = sys.argv[1] +ossl_root = sys.argv[2] + +def ff(s): + return os.path.join(ff_root, s) +def ossl(s): + return os.path.join(ossl_root, s) ##### # Read the cpp file to understand what Ciphers map to what name : # Make "ciphers" a map from name used in the javascript to a cipher macro name -fileA = open('security/manager/ssl/src/nsNSSComponent.cpp','r') +fileA = open(ff('security/manager/ssl/src/nsNSSComponent.cpp'),'r') # The input format is a file containing exactly one section of the form: # static CipherPref CipherPrefs[] = { @@ -49,7 +66,7 @@ for line in cipherLines: # Build a map enabled_ciphers from javascript name to "true" or "false", # and an (unordered!) list of the macro names for those ciphers that are # enabled. -fileB = open('netwerk/base/public/security-prefs.js', 'r') +fileB = open(ff('netwerk/base/public/security-prefs.js'), 'r') enabled_ciphers = {} for line in fileB: @@ -66,15 +83,18 @@ for k, v in enabled_ciphers.items(): if v == "true": used_ciphers.append(ciphers[k]) -oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h', - '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h', - '/usr/include/openssl/tls1.h') +#oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h', +# '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h', +# '/usr/include/openssl/tls1.h') +oSSLinclude = ('ssl/ssl3.h', 'ssl/ssl.h', + 'ssl/ssl2.h', 'ssl/ssl23.h', + 'ssl/tls1.h') ##### # This reads the hex code for the ciphers that are used by firefox. # sslProtoD is set to a map from macro name to macro value in sslproto.h; # cipher_codes is set to an (unordered!) list of these hex values. -sslProto = open('security/nss/lib/ssl/sslproto.h', 'r') +sslProto = open(ff('security/nss/lib/ssl/sslproto.h'), 'r') sslProtoD = {} for line in sslProto: @@ -93,7 +113,7 @@ for x in used_ciphers: # macro names for those files. cipher_hex = {} for fl in oSSLinclude: - fp = open(fl, 'r') + fp = open(ossl(fl), 'r') for line in fp.readlines(): m = re.match('#define\s+(\S+)\s+(\S+)', line) if m: From c5dca8f20886f797d079e6de1547dd2e42a9b222 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Mar 2012 17:53:17 -0400 Subject: [PATCH 4/8] Try to make get_mozilla_ciphers output the right macros in the right order --- src/common/get_mozilla_ciphers.py | 81 ++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py index 9b8af2e0ad..629b4dc58f 100644 --- a/src/common/get_mozilla_ciphers.py +++ b/src/common/get_mozilla_ciphers.py @@ -54,11 +54,35 @@ fileA.close() # Parse the lines and put them into a dict ciphers = {} +cipher_pref = {} for line in cipherLines: m = re.search(r'^{\s*\"([^\"]+)\",\s*(\S*)\s*}', line) if m: key,value = m.groups() ciphers[key] = value + cipher_pref[value] = key + +#### +# Now find the correct order for the ciphers +fileC = open(ff('security/nss/lib/ssl/sslenum.c'), 'r') +firefox_ciphers = [] +inEnum=False +for line in fileC: + if not inEnum: + if "SSL_ImplementedCiphers[] =" in line: + inEnum = True + continue + + if line.startswith("};"): + break + + m = re.match(r'^\s*([A-Z_0-9]+)\s*', line) + if m: + if m.group(1) == "0": + break + firefox_ciphers.append(m.group(1)) + +fileC.close() ##### # Read the JS file to understand what ciphers are enabled. The format is @@ -111,28 +135,59 @@ for x in used_ciphers: #### # Now read through all the openssl include files, and try to find the openssl # macro names for those files. -cipher_hex = {} +openssl_macro_by_hex = {} +all_openssl_macros = {} for fl in oSSLinclude: fp = open(ossl(fl), 'r') for line in fp.readlines(): m = re.match('#define\s+(\S+)\s+(\S+)', line) if m: value,key = m.groups() - if key.startswith('0x'): + if key.startswith('0x') and "_CK_" in value: key = key.replace('0x0300','0x').lower() #print "%s %s" % (key, value) - cipher_hex[key] = value + openssl_macro_by_hex[key] = value + all_openssl_macros[value]=key fp.close() # Now generate the output. -for x in cipher_codes: - try: - res = """#ifdef %s - CIPHER(%s, %s) - #else - XCIPHER(%s, %s) - #endif""" % (cipher_hex[x], x, cipher_hex[x], x, cipher_hex[x]) - print res - except KeyError: - print "Not found %s" % x +print """\ +/* This is an include file used to define the list of ciphers clients should + * advertise. Before including it, you should define the CIPHER and XCIPHER + * macros. + * + * This file was automatically generated by get_mozilla_ciphers.py. + */""" +# Go in order by the order in CipherPrefs +for firefox_macro in firefox_ciphers: + try: + js_cipher_name = cipher_pref[firefox_macro] + except KeyError: + # This one has no javascript preference. + continue + + # The cipher needs to be enabled in security-prefs.js + if enabled_ciphers.get(js_cipher_name, 'false') != 'true': + continue + + hexval = sslProtoD[firefox_macro] + + try: + openssl_macro = openssl_macro_by_hex[hexval.lower()] + openssl_macro = openssl_macro.replace("_CK_", "_TXT_") + if openssl_macro not in all_openssl_macros: + raise KeyError() + format = {'hex':hexval, 'macro':openssl_macro, 'note':""} + except KeyError: + # openssl doesn't have a macro for this. + format = {'hex':hexval, 'macro':firefox_macro, + 'note':"/* No openssl macro found for "+hexval+" */\n"} + + res = """\ +%(note)s#ifdef %(macro)s + CIPHER(%(hex)s, %(macro)s) +#else + XCIPHER(%(hex)s, %(macro)s) +#endif""" % format + print res From c2ed9a2940fa33bad94efa26fac9b45446731b67 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Mar 2012 18:19:21 -0400 Subject: [PATCH 5/8] get_mozilla_ciphers: output lowercase hex. --- src/common/get_mozilla_ciphers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py index 629b4dc58f..61e8c4d90d 100644 --- a/src/common/get_mozilla_ciphers.py +++ b/src/common/get_mozilla_ciphers.py @@ -171,7 +171,7 @@ for firefox_macro in firefox_ciphers: if enabled_ciphers.get(js_cipher_name, 'false') != 'true': continue - hexval = sslProtoD[firefox_macro] + hexval = sslProtoD[firefox_macro].lower() try: openssl_macro = openssl_macro_by_hex[hexval.lower()] From bd0657602d54be7b0c9032c40f73a0b738b0282c Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 14 Mar 2012 18:36:58 -0400 Subject: [PATCH 6/8] get_mozilla_ciphers: look at ssl3con.c, not sslenum.c --- src/common/get_mozilla_ciphers.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py index 61e8c4d90d..f9250317b1 100644 --- a/src/common/get_mozilla_ciphers.py +++ b/src/common/get_mozilla_ciphers.py @@ -64,22 +64,20 @@ for line in cipherLines: #### # Now find the correct order for the ciphers -fileC = open(ff('security/nss/lib/ssl/sslenum.c'), 'r') +fileC = open(ff('security/nss/lib/ssl/ssl3con.c'), 'r') firefox_ciphers = [] inEnum=False for line in fileC: if not inEnum: - if "SSL_ImplementedCiphers[] =" in line: + if "ssl3CipherSuiteCfg cipherSuites[" in line: inEnum = True continue if line.startswith("};"): break - m = re.match(r'^\s*([A-Z_0-9]+)\s*', line) + m = re.match(r'^\s*\{\s*([A-Z_0-9]+),', line) if m: - if m.group(1) == "0": - break firefox_ciphers.append(m.group(1)) fileC.close() From 89c16890095d63cc6f56a378108efc3d3f063204 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 May 2012 15:25:54 -0400 Subject: [PATCH 7/8] Change our ciphersuite list to match ff8 --- changes/bug4744 | 4 +++ src/common/ciphers.inc | 47 ++++++++++++++++++++++++++++--- src/common/get_mozilla_ciphers.py | 1 + 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 changes/bug4744 diff --git a/changes/bug4744 b/changes/bug4744 new file mode 100644 index 0000000000..1563cd1143 --- /dev/null +++ b/changes/bug4744 @@ -0,0 +1,4 @@ + o Major features: + - Update cipher cipher list to match Firefox 8 and later. Fix for + issue 4744. + diff --git a/src/common/ciphers.inc b/src/common/ciphers.inc index c84620d49e..137d78b117 100644 --- a/src/common/ciphers.inc +++ b/src/common/ciphers.inc @@ -1,6 +1,9 @@ /* This is an include file used to define the list of ciphers clients should * advertise. Before including it, you should define the CIPHER and XCIPHER - * macros. */ + * macros. + * + * This file was automatically generated by get_mozilla_ciphers.py. + */ #ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CIPHER(0xc00a, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) #else @@ -11,6 +14,16 @@ #else XCIPHER(0xc014, TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA) #endif +#ifdef TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + CIPHER(0x0088, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA) +#else + XCIPHER(0x0088, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA) +#endif +#ifdef TLS1_TXT_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA + CIPHER(0x0087, TLS1_TXT_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA) +#else + XCIPHER(0x0087, TLS1_TXT_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA) +#endif #ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA CIPHER(0x0039, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) #else @@ -31,6 +44,11 @@ #else XCIPHER(0xc005, TLS1_TXT_ECDH_ECDSA_WITH_AES_256_CBC_SHA) #endif +#ifdef TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA + CIPHER(0x0084, TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA) +#else + XCIPHER(0x0084, TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA) +#endif #ifdef TLS1_TXT_RSA_WITH_AES_256_SHA CIPHER(0x0035, TLS1_TXT_RSA_WITH_AES_256_SHA) #else @@ -56,6 +74,16 @@ #else XCIPHER(0xc013, TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA) #endif +#ifdef TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + CIPHER(0x0045, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA) +#else + XCIPHER(0x0045, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA) +#endif +#ifdef TLS1_TXT_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA + CIPHER(0x0044, TLS1_TXT_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA) +#else + XCIPHER(0x0044, TLS1_TXT_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA) +#endif #ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_SHA CIPHER(0x0033, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) #else @@ -86,6 +114,16 @@ #else XCIPHER(0xc004, TLS1_TXT_ECDH_ECDSA_WITH_AES_128_CBC_SHA) #endif +#ifdef TLS1_TXT_RSA_WITH_SEED_SHA + CIPHER(0x0096, TLS1_TXT_RSA_WITH_SEED_SHA) +#else + XCIPHER(0x0096, TLS1_TXT_RSA_WITH_SEED_SHA) +#endif +#ifdef TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA + CIPHER(0x0041, TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA) +#else + XCIPHER(0x0041, TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA) +#endif #ifdef SSL3_TXT_RSA_RC4_128_MD5 CIPHER(0x0004, SSL3_TXT_RSA_RC4_128_MD5) #else @@ -131,10 +169,11 @@ #else XCIPHER(0xc003, TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA) #endif -#ifdef SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA - CIPHER(0xfeff, SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA) +/* No openssl macro found for 0xfeff */ +#ifdef SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA + CIPHER(0xfeff, SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA) #else - XCIPHER(0xfeff, SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA) + XCIPHER(0xfeff, SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA) #endif #ifdef SSL3_TXT_RSA_DES_192_CBC3_SHA CIPHER(0x000a, SSL3_TXT_RSA_DES_192_CBC3_SHA) diff --git a/src/common/get_mozilla_ciphers.py b/src/common/get_mozilla_ciphers.py index f9250317b1..c7e9a84a0e 100644 --- a/src/common/get_mozilla_ciphers.py +++ b/src/common/get_mozilla_ciphers.py @@ -2,6 +2,7 @@ # coding=utf-8 # Copyright 2011, The Tor Project, Inc # original version by Arturo Filastò +# See LICENSE for licensing information # This script parses Firefox and OpenSSL sources, and uses this information # to generate a ciphers.inc file. From df6bd478eeb8164b99156bf9528e1b058fe491fd Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 15 May 2012 15:32:18 -0400 Subject: [PATCH 8/8] Implement the client side of proposal 198 This is a feature removal: we no longer fake any ciphersuite other than the not-really-standard SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA (0xfeff). This change will let servers rely on our actually supporting what we claim to support, and thereby let Tor migrate to better TLS ciphersuites. As a drawback, Tor instances that use old openssl versions and openssl builds with ciphers disabled will no longer give the "firefox" cipher list. --- changes/prop198 | 12 ++++++++ src/common/tortls.c | 72 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 changes/prop198 diff --git a/changes/prop198 b/changes/prop198 new file mode 100644 index 0000000000..fd666864c3 --- /dev/null +++ b/changes/prop198 @@ -0,0 +1,12 @@ + o Removed features: + + - Remove support for clients claiming to support any standard + ciphersuites that we can actually provide. (As of modern + OpenSSL versions, it's not necessary to fake any standard + ciphersuite, and doing so prevents us from using better + ciphersuites in the future, since servers can't know whether an + advertised ciphersuite is really supported or not.) Some + hosts--notably, ones with very old versions of OpenSSL or where + OpenSSL has been built with ECC disabled-- will stand out + because of this change; TBB users should not be affected. + This implements the client side of proposal 198. diff --git a/src/common/tortls.c b/src/common/tortls.c index 4c9d2188d4..6ffd822425 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -593,6 +593,9 @@ tor_tls_create_certificate(crypto_pk_env_t *rsa, * our OpenSSL doesn't know about. */ static const char CLIENT_CIPHER_LIST[] = #include "./ciphers.inc" + /* Tell it not to use SSLv2 ciphers, so that it can select an SSLv3 version + * of any cipher we say. */ + "!SSLv2" ; #undef CIPHER #undef XCIPHER @@ -984,11 +987,35 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) } #endif +/** Explain which ciphers we're missing. */ +static void +log_unsupported_ciphers(smartlist_t *unsupported) +{ + char *joined; + + log_notice(LD_NET, "We weren't able to find support for all of the " + "TLS ciphersuites that we wanted to advertise. This won't " + "hurt security, but it might make your Tor (if run as a client) " + "more easy for censors to block."); + + if (SSLeay() < 0x10000000L) { + log_notice(LD_NET, "To correct this, use a more recent OpenSSL, " + "built without disabling any secure ciphers or features."); + } else { + log_notice(LD_NET, "To correct this, use a version of OpenSSL " + "built with none of its ciphers disabled."); + } + + joined = smartlist_join_strings(unsupported, ":", 0, NULL); + log_info(LD_NET, "The unsupported ciphers were: %s", joined); + tor_free(joined); +} + /** Replace *ciphers with a new list of SSL ciphersuites: specifically, - * a list designed to mimic a common web browser. Some of the ciphers in the - * list won't actually be implemented by OpenSSL: that's okay so long as the - * server doesn't select them, and the server won't select anything besides - * what's in SERVER_CIPHER_LIST. + * a list designed to mimic a common web browser. We might not be able to do + * that if OpenSSL doesn't support all the ciphers we want. Some of the + * ciphers in the list won't actually be implemented by OpenSSL: that's okay + * so long as the server doesn't select them. * * [If the server does select a bogus cipher, we won't crash or * anything; we'll just fail later when we try to look up the cipher in @@ -1000,14 +1027,17 @@ rectify_client_ciphers(STACK_OF(SSL_CIPHER) **ciphers) #ifdef V2_HANDSHAKE_CLIENT if (PREDICT_UNLIKELY(!CLIENT_CIPHER_STACK)) { /* We need to set CLIENT_CIPHER_STACK to an array of the ciphers - * we want.*/ + * we want to use/advertise. */ int i = 0, j = 0; + smartlist_t *unsupported = smartlist_create(); /* First, create a dummy SSL_CIPHER for every cipher. */ CLIENT_CIPHER_DUMMIES = tor_malloc_zero(sizeof(SSL_CIPHER)*N_CLIENT_CIPHERS); for (i=0; i < N_CLIENT_CIPHERS; ++i) { CLIENT_CIPHER_DUMMIES[i].valid = 1; + /* The "3<<24" here signifies that the cipher is supposed to work with + * SSL3 and TLS1. */ CLIENT_CIPHER_DUMMIES[i].id = CLIENT_CIPHER_INFO_LIST[i].id | (3<<24); CLIENT_CIPHER_DUMMIES[i].name = CLIENT_CIPHER_INFO_LIST[i].name; } @@ -1022,27 +1052,49 @@ rectify_client_ciphers(STACK_OF(SSL_CIPHER) **ciphers) } /* Then copy as many ciphers as we can from the good list, inserting - * dummies as needed. */ - j=0; - for (i = 0; i < N_CLIENT_CIPHERS; ) { + * dummies as needed. Let j be an index into list of ciphers we have + * (*ciphers) and let i be an index into the ciphers we want + * (CLIENT_INFO_CIPHER_LIST). We are building a list of ciphers in + * CLIENT_CIPHER_STACK. + */ + for (i = j = 0; i < N_CLIENT_CIPHERS; ) { SSL_CIPHER *cipher = NULL; if (j < sk_SSL_CIPHER_num(*ciphers)) cipher = sk_SSL_CIPHER_value(*ciphers, j); if (cipher && ((cipher->id >> 24) & 0xff) != 3) { - log_debug(LD_NET, "Skipping v2 cipher %s", cipher->name); + /* Skip over non-v3 ciphers entirely. (This should no longer be + * needed, thanks to saying !SSLv2 above.) */ + log_debug(LD_NET, "Skipping v%d cipher %s", + (int)((cipher->id>>24) & 0xff), + cipher->name); ++j; } else if (cipher && (cipher->id & 0xffff) == CLIENT_CIPHER_INFO_LIST[i].id) { + /* "cipher" is the cipher we expect. Put it on the list. */ log_debug(LD_NET, "Found cipher %s", cipher->name); sk_SSL_CIPHER_push(CLIENT_CIPHER_STACK, cipher); ++j; ++i; - } else { + } else if (!strcmp(CLIENT_CIPHER_DUMMIES[i].name, + "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA")) { + /* We found bogus cipher 0xfeff, which OpenSSL doesn't support and + * never has. For this one, we need a dummy. */ log_debug(LD_NET, "Inserting fake %s", CLIENT_CIPHER_DUMMIES[i].name); sk_SSL_CIPHER_push(CLIENT_CIPHER_STACK, &CLIENT_CIPHER_DUMMIES[i]); ++i; + } else { + /* OpenSSL doesn't have this one. */ + log_debug(LD_NET, "Completely omitting unsupported cipher %s", + CLIENT_CIPHER_INFO_LIST[i].name); + smartlist_add(unsupported, (char*) CLIENT_CIPHER_INFO_LIST[i].name); + ++i; } } + + if (smartlist_len(unsupported)) + log_unsupported_ciphers(unsupported); + + smartlist_free(unsupported); } sk_SSL_CIPHER_free(*ciphers);