Merge branch 'tor-github/pr/960' into maint-0.4.0
[tor.git] / scripts / codegen / get_mozilla_ciphers.py
blobf23f2f1e6f1ee7cd0d046ced750900326ed308e7
1 #!/usr/bin/python
2 # coding=utf-8
3 # Copyright 2011-2019, The Tor Project, Inc
4 # original version by Arturo Filastò
5 # See LICENSE for licensing information
7 # This script parses Firefox and OpenSSL sources, and uses this information
8 # to generate a ciphers.inc file.
10 # It takes two arguments: the location of a firefox source directory, and the
11 # location of an openssl source directory.
13 import os
14 import re
15 import sys
17 if len(sys.argv) != 3:
18 print >>sys.stderr, "Syntax: get_mozilla_ciphers.py <firefox-source-dir> <openssl-source-dir>"
19 sys.exit(1)
21 ff_root = sys.argv[1]
22 ossl_root = sys.argv[2]
24 def ff(s):
25 return os.path.join(ff_root, s)
26 def ossl(s):
27 return os.path.join(ossl_root, s)
29 #####
30 # Read the cpp file to understand what Ciphers map to what name :
31 # Make "ciphers" a map from name used in the javascript to a cipher macro name
32 fileA = open(ff('security/manager/ssl/nsNSSComponent.cpp'),'r')
34 # The input format is a file containing exactly one section of the form:
35 # static CipherPref CipherPrefs[] = {
36 # {"name", MACRO_NAME}, // comment
37 # ...
38 # {NULL, 0}
39 # }
41 inCipherSection = False
42 cipherLines = []
43 for line in fileA:
44 if line.startswith('static const CipherPref sCipherPrefs[]'):
45 # Get the starting boundary of the Cipher Preferences
46 inCipherSection = True
47 elif inCipherSection:
48 line = line.strip()
49 if line.startswith('{ nullptr, 0}'):
50 # At the ending boundary of the Cipher Prefs
51 break
52 else:
53 cipherLines.append(line)
54 fileA.close()
56 # Parse the lines and put them into a dict
57 ciphers = {}
58 cipher_pref = {}
59 key_pending = None
60 for line in cipherLines:
61 m = re.search(r'^{\s*\"([^\"]+)\",\s*(\S+)\s*(?:,\s*(true|false))?\s*}', line)
62 if m:
63 assert not key_pending
64 key,value,enabled = m.groups()
65 if enabled == 'true':
66 ciphers[key] = value
67 cipher_pref[value] = key
68 continue
69 m = re.search(r'^{\s*\"([^\"]+)\",', line)
70 if m:
71 assert not key_pending
72 key_pending = m.group(1)
73 continue
74 m = re.search(r'^\s*(\S+)(?:,\s*(true|false))+\s*}', line)
75 if m:
76 assert key_pending
77 key = key_pending
78 value,enabled = m.groups()
79 key_pending = None
80 if enabled == 'true':
81 ciphers[key] = value
82 cipher_pref[value] = key
84 ####
85 # Now find the correct order for the ciphers
86 fileC = open(ff('security/nss/lib/ssl/ssl3con.c'), 'r')
87 firefox_ciphers = []
88 inEnum=False
89 for line in fileC:
90 if not inEnum:
91 if "ssl3CipherSuiteCfg cipherSuites[" in line:
92 inEnum = True
93 continue
95 if line.startswith("};"):
96 break
98 m = re.match(r'^\s*\{\s*([A-Z_0-9]+),', line)
99 if m:
100 firefox_ciphers.append(m.group(1))
102 fileC.close()
104 #####
105 # Read the JS file to understand what ciphers are enabled. The format is
106 # pref("name", true/false);
107 # Build a map enabled_ciphers from javascript name to "true" or "false",
108 # and an (unordered!) list of the macro names for those ciphers that are
109 # enabled.
110 fileB = open(ff('netwerk/base/security-prefs.js'), 'r')
112 enabled_ciphers = {}
113 for line in fileB:
114 m = re.match(r'pref\(\"([^\"]+)\"\s*,\s*(\S*)\s*\)', line)
115 if not m:
116 continue
117 key, val = m.groups()
118 if key.startswith("security.ssl3"):
119 enabled_ciphers[key] = val
120 fileB.close()
122 used_ciphers = []
123 for k, v in enabled_ciphers.items():
124 if v == "true":
125 used_ciphers.append(ciphers[k])
127 #oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h',
128 # '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h',
129 # '/usr/include/openssl/tls1.h')
130 oSSLinclude = ['ssl3.h', 'ssl.h'
131 'ssl2.h', 'ssl23.h',
132 'tls1.h']
134 #####
135 # This reads the hex code for the ciphers that are used by firefox.
136 # sslProtoD is set to a map from macro name to macro value in sslproto.h;
137 # cipher_codes is set to an (unordered!) list of these hex values.
138 sslProto = open(ff('security/nss/lib/ssl/sslproto.h'), 'r')
139 sslProtoD = {}
141 for line in sslProto:
142 m = re.match('#define\s+(\S+)\s+(\S+)', line)
143 if m:
144 key, value = m.groups()
145 sslProtoD[key] = value
146 sslProto.close()
148 cipher_codes = []
149 for x in used_ciphers:
150 cipher_codes.append(sslProtoD[x].lower())
152 ####
153 # Now read through all the openssl include files, and try to find the openssl
154 # macro names for those files.
155 openssl_macro_by_hex = {}
156 all_openssl_macros = {}
157 for fl in oSSLinclude:
158 fname = ossl("include/openssl/"+fl)
159 if not os.path.exists(fname):
160 continue
161 fp = open(fname, 'r')
162 for line in fp.readlines():
163 m = re.match('# *define\s+(\S+)\s+(\S+)', line)
164 if m:
165 value,key = m.groups()
166 if key.startswith('0x') and "_CK_" in value:
167 key = key.replace('0x0300','0x').lower()
168 #print "%s %s" % (key, value)
169 openssl_macro_by_hex[key] = value
170 all_openssl_macros[value]=key
171 fp.close()
173 # Now generate the output.
174 print """\
175 /* This is an include file used to define the list of ciphers clients should
176 * advertise. Before including it, you should define the CIPHER and XCIPHER
177 * macros.
179 * This file was automatically generated by get_mozilla_ciphers.py.
180 */"""
181 # Go in order by the order in CipherPrefs
182 for firefox_macro in firefox_ciphers:
184 try:
185 js_cipher_name = cipher_pref[firefox_macro]
186 except KeyError:
187 # This one has no javascript preference.
188 continue
190 # The cipher needs to be enabled in security-prefs.js
191 if enabled_ciphers.get(js_cipher_name, 'false') != 'true':
192 continue
194 hexval = sslProtoD[firefox_macro].lower()
196 try:
197 openssl_macro = openssl_macro_by_hex[hexval.lower()]
198 openssl_macro = openssl_macro.replace("_CK_", "_TXT_")
199 if openssl_macro not in all_openssl_macros:
200 raise KeyError()
201 format = {'hex':hexval, 'macro':openssl_macro, 'note':""}
202 except KeyError:
203 # openssl doesn't have a macro for this.
204 format = {'hex':hexval, 'macro':firefox_macro,
205 'note':"/* No openssl macro found for "+hexval+" */\n"}
207 res = """\
208 %(note)s#ifdef %(macro)s
209 CIPHER(%(hex)s, %(macro)s)
210 #else
211 XCIPHER(%(hex)s, %(macro)s)
212 #endif""" % format
213 print res