get_mozilla_ciphers.py (6836B)
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 6 7 # This script parses Firefox and OpenSSL sources, and uses this information 8 # to generate a ciphers.inc file. 9 # 10 # It takes two arguments: the location of a firefox source directory, and the 11 # location of an openssl source directory. 12 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 17 18 import os 19 import re 20 import sys 21 import yaml 22 23 if len(sys.argv) != 3: 24 print("Syntax: get_mozilla_ciphers.py <firefox-source-dir> <openssl-source-dir>", file=sys.stderr) 25 sys.exit(1) 26 27 ff_root = sys.argv[1] 28 ossl_root = sys.argv[2] 29 30 def ff(s): 31 return os.path.join(ff_root, s) 32 def ossl(s): 33 return os.path.join(ossl_root, s) 34 35 ##### 36 # Read the cpp file to understand what Ciphers map to what name : 37 # Make "ciphers" a map from name used in the javascript to a cipher macro name 38 fileA = open(ff('security/manager/ssl/nsNSSComponent.cpp'),'r') 39 40 # The input format is a file containing exactly one section of the form: 41 # static const CipherPref sCipherPrefs[] = { 42 # {"security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", 43 # TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 44 # StaticPrefs::security_ssl3_ecdhe_rsa_aes_128_gcm_sha256}, 45 # {"security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", 46 # TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 47 # StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_128_gcm_sha256}, 48 # ... 49 # }, 50 # }; 51 52 inCipherSection = False 53 cipherLines = [] 54 for line in fileA: 55 if line.startswith('static const CipherPref sCipherPrefs[]'): 56 # Get the starting boundary of the Cipher Preferences 57 inCipherSection = True 58 elif inCipherSection: 59 line = line.strip() 60 if line.startswith('};'): 61 # At the ending boundary of the Cipher Prefs 62 break 63 else: 64 cipherLines.append(line) 65 fileA.close() 66 67 # Parse the lines and put them into a dict 68 ciphers = {} 69 cipher_pref = {} 70 cipherLines = " ".join(cipherLines) 71 pat = re.compile( 72 r''' 73 \"security. ([^\"]+) \", \s* 74 ([^\s,]+)\s*, \s* 75 StaticPrefs::security_(\S+) 76 ''', 77 re.X) 78 while cipherLines: 79 c = cipherLines.split("}", maxsplit=1) 80 if len(c) == 2: 81 cipherLines = c[1] 82 else: 83 cipherLines = "" 84 line = c[0] 85 86 m = pat.search(line) 87 if not m: 88 if line != ",": 89 print("can't parse:", line) 90 continue 91 92 ident = m.group(1).replace(".", "_") 93 94 ciphers[ident] = m.group(2) 95 96 cipher_pref[m.group(2)] = m.group(3) 97 98 continue 99 100 #### 101 # Now find the correct order for the ciphers 102 fileC = open(ff('security/nss/lib/ssl/ssl3con.c'), 'r') 103 firefox_ciphers = [] 104 inEnum=False 105 for line in fileC: 106 if not inEnum: 107 if "ssl3CipherSuiteCfg cipherSuites[" in line: 108 inEnum = True 109 continue 110 111 if line.startswith("};"): 112 break 113 114 m = re.match(r'^\s*\{\s*([A-Z_0-9]+),', line) 115 if m: 116 firefox_ciphers.append(m.group(1)) 117 118 fileC.close() 119 120 121 ##### 122 # Read the yaml file where the preferences are defined. 123 124 fileB = open(ff('modules/libpref/init/StaticPrefList.yaml'), 'r').read() 125 fileB, _ = re.subn(r'@([^@]*)@', r'"\1"', fileB) 126 127 yaml_file = yaml.load(fileB, Loader=yaml.Loader) 128 129 enabled_ciphers = {} 130 for entry in yaml_file: 131 name = entry['name'] 132 if name.startswith("security.ssl3.") and "deprecated" not in name: 133 name = name.removeprefix("security.") 134 name = name.replace(".", "_") 135 enabled_ciphers[name] = entry['value'] 136 137 used_ciphers = [] 138 for k, v in enabled_ciphers.items(): 139 if v in (True, "True", "true", "IS_NOT_EARLY_BETA_OR_EARLIER"): 140 used_ciphers.append(ciphers[k]) 141 elif v == False: 142 pass 143 else: 144 print(f"Warning: unexpected value {v!r} for 'enabled'", file=sys.stderr) 145 sys.exit(1) 146 147 #oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h', 148 # '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h', 149 # '/usr/include/openssl/tls1.h') 150 oSSLinclude = ['ssl3.h', 'ssl.h' 151 'ssl2.h', 'ssl23.h', 152 'tls1.h'] 153 154 ##### 155 # This reads the hex code for the ciphers that are used by firefox. 156 # sslProtoD is set to a map from macro name to macro value in sslproto.h; 157 # cipher_codes is set to an (unordered!) list of these hex values. 158 sslProto = open(ff('security/nss/lib/ssl/sslproto.h'), 'r') 159 sslProtoD = {} 160 161 for line in sslProto: 162 m = re.match(r'#define\s+(\S+)\s+(\S+)', line) 163 if m: 164 key, value = m.groups() 165 sslProtoD[key] = value 166 sslProto.close() 167 168 cipher_codes = [] 169 for x in used_ciphers: 170 cipher_codes.append(sslProtoD[x].lower()) 171 172 #### 173 # Now read through all the openssl include files, and try to find the openssl 174 # macro names for those files. 175 openssl_macro_by_hex = {} 176 all_openssl_macros = {} 177 for fl in oSSLinclude: 178 fname = ossl("include/openssl/"+fl) 179 if not os.path.exists(fname): 180 continue 181 fp = open(fname, 'r') 182 for line in fp.readlines(): 183 m = re.match(r'# *define\s+(\S+)\s+(\S+)', line) 184 if m: 185 value,key = m.groups() 186 if key.startswith('0x') and "_CK_" in value: 187 key = key.replace('0x0300','0x').lower() 188 #print "%s %s" % (key, value) 189 openssl_macro_by_hex[key] = value 190 all_openssl_macros[value]=key 191 fp.close() 192 193 # Now generate the output. 194 print("""\ 195 /* This is an include file used to define the list of ciphers clients should 196 * advertise. Before including it, you should define the CIPHER and XCIPHER 197 * macros. 198 * 199 * This file was automatically generated by get_mozilla_ciphers.py. 200 */""") 201 # Go in order by the order in CipherPrefs 202 for firefox_macro in firefox_ciphers: 203 204 try: 205 cipher_pref_name = cipher_pref[firefox_macro] 206 except KeyError: 207 # This one has no javascript preference. 208 continue 209 210 # The cipher needs to be enabled in security-prefs.js 211 if not enabled_ciphers.get(cipher_pref_name): 212 continue 213 214 hexval = sslProtoD[firefox_macro].lower() 215 216 try: 217 openssl_macro = openssl_macro_by_hex[hexval.lower()] 218 openssl_macro = openssl_macro.replace("_CK_", "_TXT_") 219 if openssl_macro not in all_openssl_macros: 220 raise KeyError() 221 format = {'hex':hexval, 'macro':openssl_macro, 'note':""} 222 except KeyError: 223 # openssl doesn't have a macro for this. 224 format = {'hex':hexval, 'macro':firefox_macro, 225 'note':"/* No openssl macro found for "+hexval+" */\n"} 226 227 res = """\ 228 %(note)s#ifdef %(macro)s 229 CIPHER(%(hex)s, %(macro)s) 230 #else 231 XCIPHER(%(hex)s, %(macro)s) 232 #endif""" % format 233 print(res)