tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

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)