Merge branch 'tor-github/pr/960' into maint-0.4.0
[tor.git] / scripts / codegen / gen_server_ciphers.py
blob5d326f8b9e97476c6b4909caef0c7006a2d0d730
1 #!/usr/bin/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 import re
12 import sys
14 EPHEMERAL_INDICATORS = [ "_EDH_", "_DHE_", "_ECDHE_" ]
15 BAD_STUFF = [ "_DES_40_", "MD5", "_RC4_", "_DES_64_",
16 "_SEED_", "_CAMELLIA_", "_NULL",
17 "_CCM_8", "_DES_", ]
19 # these never get #ifdeffed.
20 MANDATORY = [
21 "TLS1_TXT_DHE_RSA_WITH_AES_256_SHA",
22 "TLS1_TXT_DHE_RSA_WITH_AES_128_SHA",
25 def find_ciphers(filename):
26 with open(filename) as f:
27 for line in f:
28 m = re.search(r'(?:SSL3|TLS1)_TXT_\w+', line)
29 if m:
30 yield m.group(0)
32 def usable_cipher(ciph):
33 ephemeral = False
34 for e in EPHEMERAL_INDICATORS:
35 if e in ciph:
36 ephemeral = True
37 if not ephemeral:
38 return False
40 if "_RSA_" not in ciph:
41 return False
43 for b in BAD_STUFF:
44 if b in ciph:
45 return False
46 return True
48 # All fields we sort on, in order of priority.
49 FIELDS = [ 'cipher', 'fwsec', 'mode', 'digest', 'bitlength' ]
50 # Map from sorted fields to recognized value in descending order of goodness
51 FIELD_VALS = { 'cipher' : [ 'AES', 'CHACHA20' ],
52 'fwsec' : [ 'ECDHE', 'DHE' ],
53 'mode' : [ 'POLY1305', 'GCM', 'CCM', 'CBC', ],
54 'digest' : [ 'n/a', 'SHA384', 'SHA256', 'SHA', ],
55 'bitlength' : [ '256', '128', '192' ],
58 class Ciphersuite(object):
59 def __init__(self, name, fwsec, cipher, bitlength, mode, digest):
60 if fwsec == 'EDH':
61 fwsec = 'DHE'
63 if mode in [ '_CBC3', '_CBC', '' ]:
64 mode = 'CBC'
65 elif mode == '_GCM':
66 mode = 'GCM'
68 self.name = name
69 self.fwsec = fwsec
70 self.cipher = cipher
71 self.bitlength = bitlength
72 self.mode = mode
73 self.digest = digest
75 for f in FIELDS:
76 assert(getattr(self, f) in FIELD_VALS[f])
78 def sort_key(self):
79 return tuple(FIELD_VALS[f].index(getattr(self,f)) for f in FIELDS)
82 def parse_cipher(ciph):
83 m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_(AES|DES)_(256|128|192)(|_CBC|_CBC3|_GCM)_(SHA|SHA256|SHA384)$', ciph)
85 if m:
86 fwsec, cipher, bits, mode, digest = m.groups()
87 return Ciphersuite(ciph, fwsec, cipher, bits, mode, digest)
89 m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_(AES|DES)_(256|128|192)_CCM', ciph)
90 if m:
91 fwsec, cipher, bits = m.groups()
92 return Ciphersuite(ciph, fwsec, cipher, bits, "CCM", "n/a")
94 m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_CHACHA20_POLY1305', ciph)
95 if m:
96 fwsec, = m.groups()
97 return Ciphersuite(ciph, fwsec, "CHACHA20", "256", "POLY1305", "n/a")
99 print "/* Couldn't parse %s ! */"%ciph
100 return None
103 ALL_CIPHERS = []
105 for fname in sys.argv[1:]:
106 for c in find_ciphers(fname):
107 if usable_cipher(c):
108 parsed = parse_cipher(c)
109 if parsed != None:
110 ALL_CIPHERS.append(parsed)
112 ALL_CIPHERS.sort(key=Ciphersuite.sort_key)
114 indent = " "*7
116 for c in ALL_CIPHERS:
117 if c is ALL_CIPHERS[-1]:
118 colon = ''
119 else:
120 colon = ' ":"'
122 if c.name in MANDATORY:
123 print "%s/* Required */"%indent
124 print '%s%s%s'%(indent,c.name,colon)
125 else:
126 print "#ifdef %s"%c.name
127 print '%s%s%s'%(indent,c.name,colon)
128 print "#endif"
130 print '%s;'%indent