gen_server_ciphers.py (3873B)
1 #!/usr/bin/env python 2 # Copyright 2014-2019, The Tor Project, Inc 3 # See LICENSE for licensing information 4 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. 8 # 9 # Run it on all the files in your openssl include directory. 10 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 15 16 import re 17 import sys 18 19 EPHEMERAL_INDICATORS = [ "_EDH_", "_DHE_", "_ECDHE_" ] 20 BAD_STUFF = [ "_DES_40_", "MD5", "_RC4_", "_DES_64_", 21 "_SEED_", "_CAMELLIA_", "_NULL", 22 "_CCM_8", "_DES_", ] 23 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", 28 ] 29 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) 36 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 44 45 if "_RSA_" not in ciph: 46 return False 47 48 for b in BAD_STUFF: 49 if b in ciph: 50 return False 51 return True 52 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' ], 61 } 62 63 class Ciphersuite(object): 64 def __init__(self, name, fwsec, cipher, bitlength, mode, digest): 65 if fwsec == 'EDH': 66 fwsec = 'DHE' 67 68 if mode in [ '_CBC3', '_CBC', '' ]: 69 mode = 'CBC' 70 elif mode == '_GCM': 71 mode = 'GCM' 72 73 self.name = name 74 self.fwsec = fwsec 75 self.cipher = cipher 76 self.bitlength = bitlength 77 self.mode = mode 78 self.digest = digest 79 80 for f in FIELDS: 81 assert(getattr(self, f) in FIELD_VALS[f]) 82 83 def sort_key(self): 84 return tuple(FIELD_VALS[f].index(getattr(self,f)) for f in FIELDS) 85 86 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) 89 90 if m: 91 fwsec, cipher, bits, mode, digest = m.groups() 92 return Ciphersuite(ciph, fwsec, cipher, bits, mode, digest) 93 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") 98 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") 103 104 print("/* Couldn't parse %s ! */"%ciph) 105 return None 106 107 108 ALL_CIPHERS = [] 109 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) 116 117 ALL_CIPHERS.sort(key=Ciphersuite.sort_key) 118 119 indent = " "*7 120 121 for c in ALL_CIPHERS: 122 if c is ALL_CIPHERS[-1]: 123 colon = '' 124 else: 125 colon = ' ":"' 126 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") 134 135 print('%s;'%indent)