Merge branch 'tap-out-phase-1' into 'main'
[tor.git] / scripts / codegen / get_mozilla_ciphers.py
blob65ef1aca2fb6f0f27cf759dffd136b798085f406
1 #!/usr/bin/env 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 # Future imports for Python 2.7, mandatory in 3.0
14 from __future__ import division
15 from __future__ import print_function
16 from __future__ import unicode_literals
18 import os
19 import re
20 import sys
22 if len(sys.argv) != 3:
23 print("Syntax: get_mozilla_ciphers.py <firefox-source-dir> <openssl-source-dir>", file=sys.stderr)
24 sys.exit(1)
26 ff_root = sys.argv[1]
27 ossl_root = sys.argv[2]
29 def ff(s):
30 return os.path.join(ff_root, s)
31 def ossl(s):
32 return os.path.join(ossl_root, s)
34 #####
35 # Read the cpp file to understand what Ciphers map to what name :
36 # Make "ciphers" a map from name used in the javascript to a cipher macro name
37 fileA = open(ff('security/manager/ssl/nsNSSComponent.cpp'),'r')
39 # The input format is a file containing exactly one section of the form:
40 # static CipherPref CipherPrefs[] = {
41 # {"name", MACRO_NAME}, // comment
42 # ...
43 # {NULL, 0}
44 # }
46 inCipherSection = False
47 cipherLines = []
48 for line in fileA:
49 if line.startswith('static const CipherPref sCipherPrefs[]'):
50 # Get the starting boundary of the Cipher Preferences
51 inCipherSection = True
52 elif inCipherSection:
53 line = line.strip()
54 if line.startswith('{ nullptr, 0}'):
55 # At the ending boundary of the Cipher Prefs
56 break
57 else:
58 cipherLines.append(line)
59 fileA.close()
61 # Parse the lines and put them into a dict
62 ciphers = {}
63 cipher_pref = {}
64 key_pending = None
65 for line in cipherLines:
66 m = re.search(r'^{\s*\"([^\"]+)\",\s*(\S+)\s*(?:,\s*(true|false))?\s*}', line)
67 if m:
68 assert not key_pending
69 key,value,enabled = m.groups()
70 if enabled == 'true':
71 ciphers[key] = value
72 cipher_pref[value] = key
73 continue
74 m = re.search(r'^{\s*\"([^\"]+)\",', line)
75 if m:
76 assert not key_pending
77 key_pending = m.group(1)
78 continue
79 m = re.search(r'^\s*(\S+)(?:,\s*(true|false))+\s*}', line)
80 if m:
81 assert key_pending
82 key = key_pending
83 value,enabled = m.groups()
84 key_pending = None
85 if enabled == 'true':
86 ciphers[key] = value
87 cipher_pref[value] = key
89 ####
90 # Now find the correct order for the ciphers
91 fileC = open(ff('security/nss/lib/ssl/ssl3con.c'), 'r')
92 firefox_ciphers = []
93 inEnum=False
94 for line in fileC:
95 if not inEnum:
96 if "ssl3CipherSuiteCfg cipherSuites[" in line:
97 inEnum = True
98 continue
100 if line.startswith("};"):
101 break
103 m = re.match(r'^\s*\{\s*([A-Z_0-9]+),', line)
104 if m:
105 firefox_ciphers.append(m.group(1))
107 fileC.close()
109 #####
110 # Read the JS file to understand what ciphers are enabled. The format is
111 # pref("name", true/false);
112 # Build a map enabled_ciphers from javascript name to "true" or "false",
113 # and an (unordered!) list of the macro names for those ciphers that are
114 # enabled.
115 fileB = open(ff('netwerk/base/security-prefs.js'), 'r')
117 enabled_ciphers = {}
118 for line in fileB:
119 m = re.match(r'pref\(\"([^\"]+)\"\s*,\s*(\S*)\s*\)', line)
120 if not m:
121 continue
122 key, val = m.groups()
123 if key.startswith("security.ssl3"):
124 enabled_ciphers[key] = val
125 fileB.close()
127 used_ciphers = []
128 for k, v in enabled_ciphers.items():
129 if v == "true":
130 used_ciphers.append(ciphers[k])
132 #oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h',
133 # '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h',
134 # '/usr/include/openssl/tls1.h')
135 oSSLinclude = ['ssl3.h', 'ssl.h'
136 'ssl2.h', 'ssl23.h',
137 'tls1.h']
139 #####
140 # This reads the hex code for the ciphers that are used by firefox.
141 # sslProtoD is set to a map from macro name to macro value in sslproto.h;
142 # cipher_codes is set to an (unordered!) list of these hex values.
143 sslProto = open(ff('security/nss/lib/ssl/sslproto.h'), 'r')
144 sslProtoD = {}
146 for line in sslProto:
147 m = re.match(r'#define\s+(\S+)\s+(\S+)', line)
148 if m:
149 key, value = m.groups()
150 sslProtoD[key] = value
151 sslProto.close()
153 cipher_codes = []
154 for x in used_ciphers:
155 cipher_codes.append(sslProtoD[x].lower())
157 ####
158 # Now read through all the openssl include files, and try to find the openssl
159 # macro names for those files.
160 openssl_macro_by_hex = {}
161 all_openssl_macros = {}
162 for fl in oSSLinclude:
163 fname = ossl("include/openssl/"+fl)
164 if not os.path.exists(fname):
165 continue
166 fp = open(fname, 'r')
167 for line in fp.readlines():
168 m = re.match(r'# *define\s+(\S+)\s+(\S+)', line)
169 if m:
170 value,key = m.groups()
171 if key.startswith('0x') and "_CK_" in value:
172 key = key.replace('0x0300','0x').lower()
173 #print "%s %s" % (key, value)
174 openssl_macro_by_hex[key] = value
175 all_openssl_macros[value]=key
176 fp.close()
178 # Now generate the output.
179 print("""\
180 /* This is an include file used to define the list of ciphers clients should
181 * advertise. Before including it, you should define the CIPHER and XCIPHER
182 * macros.
184 * This file was automatically generated by get_mozilla_ciphers.py.
185 */""")
186 # Go in order by the order in CipherPrefs
187 for firefox_macro in firefox_ciphers:
189 try:
190 js_cipher_name = cipher_pref[firefox_macro]
191 except KeyError:
192 # This one has no javascript preference.
193 continue
195 # The cipher needs to be enabled in security-prefs.js
196 if enabled_ciphers.get(js_cipher_name, 'false') != 'true':
197 continue
199 hexval = sslProtoD[firefox_macro].lower()
201 try:
202 openssl_macro = openssl_macro_by_hex[hexval.lower()]
203 openssl_macro = openssl_macro.replace("_CK_", "_TXT_")
204 if openssl_macro not in all_openssl_macros:
205 raise KeyError()
206 format = {'hex':hexval, 'macro':openssl_macro, 'note':""}
207 except KeyError:
208 # openssl doesn't have a macro for this.
209 format = {'hex':hexval, 'macro':firefox_macro,
210 'note':"/* No openssl macro found for "+hexval+" */\n"}
212 res = """\
213 %(note)s#ifdef %(macro)s
214 CIPHER(%(hex)s, %(macro)s)
215 #else
216 XCIPHER(%(hex)s, %(macro)s)
217 #endif""" % format
218 print(res)