Merge branch 'maint-0.4.6'
[tor.git] / scripts / codegen / gen_server_ciphers.py
blob8c88e54a13fced234d94eef16060646f1bc19f26
1 #!/usr/bin/env python
2 # Copyright 2014-2019, The Tor Project, Inc
3 # See LICENSE for licensing information
5 # This script parses openssl headers to find ciphersuite names, determines
6 # which ones we should be willing to use as a server, and sorts them according
7 # to preference rules.
9 # Run it on all the files in your openssl include directory.
11 # Future imports for Python 2.7, mandatory in 3.0
12 from __future__ import division
13 from __future__ import print_function
14 from __future__ import unicode_literals
16 import re
17 import sys
19 EPHEMERAL_INDICATORS = [ "_EDH_", "_DHE_", "_ECDHE_" ]
20 BAD_STUFF = [ "_DES_40_", "MD5", "_RC4_", "_DES_64_",
21 "_SEED_", "_CAMELLIA_", "_NULL",
22 "_CCM_8", "_DES_", ]
24 # these never get #ifdeffed.
25 MANDATORY = [
26 "TLS1_TXT_DHE_RSA_WITH_AES_256_SHA",
27 "TLS1_TXT_DHE_RSA_WITH_AES_128_SHA",
30 def find_ciphers(filename):
31 with open(filename) as f:
32 for line in f:
33 m = re.search(r'(?:SSL3|TLS1)_TXT_\w+', line)
34 if m:
35 yield m.group(0)
37 def usable_cipher(ciph):
38 ephemeral = False
39 for e in EPHEMERAL_INDICATORS:
40 if e in ciph:
41 ephemeral = True
42 if not ephemeral:
43 return False
45 if "_RSA_" not in ciph:
46 return False
48 for b in BAD_STUFF:
49 if b in ciph:
50 return False
51 return True
53 # All fields we sort on, in order of priority.
54 FIELDS = [ 'cipher', 'fwsec', 'mode', 'digest', 'bitlength' ]
55 # Map from sorted fields to recognized value in descending order of goodness
56 FIELD_VALS = { 'cipher' : [ 'AES', 'CHACHA20' ],
57 'fwsec' : [ 'ECDHE', 'DHE' ],
58 'mode' : [ 'POLY1305', 'GCM', 'CCM', 'CBC', ],
59 'digest' : [ 'n/a', 'SHA384', 'SHA256', 'SHA', ],
60 'bitlength' : [ '256', '128', '192' ],
63 class Ciphersuite(object):
64 def __init__(self, name, fwsec, cipher, bitlength, mode, digest):
65 if fwsec == 'EDH':
66 fwsec = 'DHE'
68 if mode in [ '_CBC3', '_CBC', '' ]:
69 mode = 'CBC'
70 elif mode == '_GCM':
71 mode = 'GCM'
73 self.name = name
74 self.fwsec = fwsec
75 self.cipher = cipher
76 self.bitlength = bitlength
77 self.mode = mode
78 self.digest = digest
80 for f in FIELDS:
81 assert(getattr(self, f) in FIELD_VALS[f])
83 def sort_key(self):
84 return tuple(FIELD_VALS[f].index(getattr(self,f)) for f in FIELDS)
87 def parse_cipher(ciph):
88 m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_(AES|DES)_(256|128|192)(|_CBC|_CBC3|_GCM)_(SHA|SHA256|SHA384)$', ciph)
90 if m:
91 fwsec, cipher, bits, mode, digest = m.groups()
92 return Ciphersuite(ciph, fwsec, cipher, bits, mode, digest)
94 m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_(AES|DES)_(256|128|192)_CCM', ciph)
95 if m:
96 fwsec, cipher, bits = m.groups()
97 return Ciphersuite(ciph, fwsec, cipher, bits, "CCM", "n/a")
99 m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_CHACHA20_POLY1305', ciph)
100 if m:
101 fwsec, = m.groups()
102 return Ciphersuite(ciph, fwsec, "CHACHA20", "256", "POLY1305", "n/a")
104 print("/* Couldn't parse %s ! */"%ciph)
105 return None
108 ALL_CIPHERS = []
110 for fname in sys.argv[1:]:
111 for c in find_ciphers(fname):
112 if usable_cipher(c):
113 parsed = parse_cipher(c)
114 if parsed != None:
115 ALL_CIPHERS.append(parsed)
117 ALL_CIPHERS.sort(key=Ciphersuite.sort_key)
119 indent = " "*7
121 for c in ALL_CIPHERS:
122 if c is ALL_CIPHERS[-1]:
123 colon = ''
124 else:
125 colon = ' ":"'
127 if c.name in MANDATORY:
128 print("%s/* Required */"%indent)
129 print('%s%s%s'%(indent,c.name,colon))
130 else:
131 print("#ifdef %s"%c.name)
132 print('%s%s%s'%(indent,c.name,colon))
133 print("#endif")
135 print('%s;'%indent)