aboutsummaryrefslogtreecommitdiff
path: root/scripts/codegen/get_mozilla_ciphers.py
blob: 0636eb365851c82f88d6777e04a1cc2500018723 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/usr/bin/python
# 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.
#
# 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 <firefox-source-dir> <openssl-source-dir>"
    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(ff('security/manager/ssl/src/nsNSSComponent.cpp'),'r')

# The input format is a file containing exactly one section of the form:
# static CipherPref CipherPrefs[] = {
#  {"name", MACRO_NAME}, // comment
#  ...
#  {NULL, 0}
# }

inCipherSection = False
cipherLines = []
for line in fileA:
    if line.startswith('static const CipherPref sCipherPrefs[]'):
        # Get the starting boundary of the Cipher Preferences
        inCipherSection = True
    elif inCipherSection:
        line = line.strip()
        if line.startswith('{ nullptr, 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 = {}
cipher_pref = {}
key_pending = None
for line in cipherLines:
    m = re.search(r'^{\s*\"([^\"]+)\",\s*(\S+)\s*(?:,\s*(true|false))?\s*}', line)
    if m:
        assert not key_pending
        key,value,enabled = m.groups()
        if enabled == 'true':
            ciphers[key] = value
            cipher_pref[value] = key
        continue
    m = re.search(r'^{\s*\"([^\"]+)\",', line)
    if m:
        assert not key_pending
        key_pending = m.group(1)
        continue
    m = re.search(r'^\s*(\S+)(?:,\s*(true|false))?\s*}', line)
    if m:
        assert key_pending
        key = key_pending
        value,enabled = m.groups()
        key_pending = None
        if enabled == 'true':
            ciphers[key] = value
            cipher_pref[value] = key

####
# Now find the correct order for the ciphers
fileC = open(ff('security/nss/lib/ssl/ssl3con.c'), 'r')
firefox_ciphers = []
inEnum=False
for line in fileC:
    if not inEnum:
        if "ssl3CipherSuiteCfg cipherSuites[" in line:
            inEnum = True
        continue

    if line.startswith("};"):
        break

    m = re.match(r'^\s*\{\s*([A-Z_0-9]+),', line)
    if m:
        firefox_ciphers.append(m.group(1))

fileC.close()

#####
# 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(ff('netwerk/base/public/security-prefs.js'), 'r')

enabled_ciphers = {}
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 = []
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 = ('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(ff('security/nss/lib/ssl/sslproto.h'), 'r')
sslProtoD = {}

for line in sslProto:
    m = re.match('#define\s+(\S+)\s+(\S+)', line)
    if m:
        key, value = m.groups()
        sslProtoD[key] = value
sslProto.close()

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.
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') and "_CK_" in value:
                key = key.replace('0x0300','0x').lower()
                #print "%s %s" % (key, value)
                openssl_macro_by_hex[key] = value
            all_openssl_macros[value]=key
    fp.close()

# Now generate the output.
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].lower()

    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