get_mozilla_ciphers.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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. # This script parses Firefox and OpenSSL sources, and uses this information
  7. # to generate a ciphers.inc file.
  8. #
  9. # It takes two arguments: the location of a firefox source directory, and the
  10. # location of an openssl source 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
  15. import os
  16. import re
  17. import sys
  18. if len(sys.argv) != 3:
  19. print("Syntax: get_mozilla_ciphers.py <firefox-source-dir> <openssl-source-dir>", file=sys.stderr)
  20. sys.exit(1)
  21. ff_root = sys.argv[1]
  22. ossl_root = sys.argv[2]
  23. def ff(s):
  24. return os.path.join(ff_root, s)
  25. def ossl(s):
  26. return os.path.join(ossl_root, s)
  27. #####
  28. # Read the cpp file to understand what Ciphers map to what name :
  29. # Make "ciphers" a map from name used in the javascript to a cipher macro name
  30. fileA = open(ff('security/manager/ssl/nsNSSComponent.cpp'),'r')
  31. # The input format is a file containing exactly one section of the form:
  32. # static CipherPref CipherPrefs[] = {
  33. # {"name", MACRO_NAME}, // comment
  34. # ...
  35. # {NULL, 0}
  36. # }
  37. inCipherSection = False
  38. cipherLines = []
  39. for line in fileA:
  40. if line.startswith('static const CipherPref sCipherPrefs[]'):
  41. # Get the starting boundary of the Cipher Preferences
  42. inCipherSection = True
  43. elif inCipherSection:
  44. line = line.strip()
  45. if line.startswith('{ nullptr, 0}'):
  46. # At the ending boundary of the Cipher Prefs
  47. break
  48. else:
  49. cipherLines.append(line)
  50. fileA.close()
  51. # Parse the lines and put them into a dict
  52. ciphers = {}
  53. cipher_pref = {}
  54. key_pending = None
  55. for line in cipherLines:
  56. m = re.search(r'^{\s*\"([^\"]+)\",\s*(\S+)\s*(?:,\s*(true|false))?\s*}', line)
  57. if m:
  58. assert not key_pending
  59. key,value,enabled = m.groups()
  60. if enabled == 'true':
  61. ciphers[key] = value
  62. cipher_pref[value] = key
  63. continue
  64. m = re.search(r'^{\s*\"([^\"]+)\",', line)
  65. if m:
  66. assert not key_pending
  67. key_pending = m.group(1)
  68. continue
  69. m = re.search(r'^\s*(\S+)(?:,\s*(true|false))+\s*}', line)
  70. if m:
  71. assert key_pending
  72. key = key_pending
  73. value,enabled = m.groups()
  74. key_pending = None
  75. if enabled == 'true':
  76. ciphers[key] = value
  77. cipher_pref[value] = key
  78. ####
  79. # Now find the correct order for the ciphers
  80. fileC = open(ff('security/nss/lib/ssl/ssl3con.c'), 'r')
  81. firefox_ciphers = []
  82. inEnum=False
  83. for line in fileC:
  84. if not inEnum:
  85. if "ssl3CipherSuiteCfg cipherSuites[" in line:
  86. inEnum = True
  87. continue
  88. if line.startswith("};"):
  89. break
  90. m = re.match(r'^\s*\{\s*([A-Z_0-9]+),', line)
  91. if m:
  92. firefox_ciphers.append(m.group(1))
  93. fileC.close()
  94. #####
  95. # Read the JS file to understand what ciphers are enabled. The format is
  96. # pref("name", true/false);
  97. # Build a map enabled_ciphers from javascript name to "true" or "false",
  98. # and an (unordered!) list of the macro names for those ciphers that are
  99. # enabled.
  100. fileB = open(ff('netwerk/base/security-prefs.js'), 'r')
  101. enabled_ciphers = {}
  102. for line in fileB:
  103. m = re.match(r'pref\(\"([^\"]+)\"\s*,\s*(\S*)\s*\)', line)
  104. if not m:
  105. continue
  106. key, val = m.groups()
  107. if key.startswith("security.ssl3"):
  108. enabled_ciphers[key] = val
  109. fileB.close()
  110. used_ciphers = []
  111. for k, v in enabled_ciphers.items():
  112. if v == "true":
  113. used_ciphers.append(ciphers[k])
  114. #oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h',
  115. # '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h',
  116. # '/usr/include/openssl/tls1.h')
  117. oSSLinclude = ['ssl3.h', 'ssl.h'
  118. 'ssl2.h', 'ssl23.h',
  119. 'tls1.h']
  120. #####
  121. # This reads the hex code for the ciphers that are used by firefox.
  122. # sslProtoD is set to a map from macro name to macro value in sslproto.h;
  123. # cipher_codes is set to an (unordered!) list of these hex values.
  124. sslProto = open(ff('security/nss/lib/ssl/sslproto.h'), 'r')
  125. sslProtoD = {}
  126. for line in sslProto:
  127. m = re.match('#define\s+(\S+)\s+(\S+)', line)
  128. if m:
  129. key, value = m.groups()
  130. sslProtoD[key] = value
  131. sslProto.close()
  132. cipher_codes = []
  133. for x in used_ciphers:
  134. cipher_codes.append(sslProtoD[x].lower())
  135. ####
  136. # Now read through all the openssl include files, and try to find the openssl
  137. # macro names for those files.
  138. openssl_macro_by_hex = {}
  139. all_openssl_macros = {}
  140. for fl in oSSLinclude:
  141. fname = ossl("include/openssl/"+fl)
  142. if not os.path.exists(fname):
  143. continue
  144. fp = open(fname, 'r')
  145. for line in fp.readlines():
  146. m = re.match('# *define\s+(\S+)\s+(\S+)', line)
  147. if m:
  148. value,key = m.groups()
  149. if key.startswith('0x') and "_CK_" in value:
  150. key = key.replace('0x0300','0x').lower()
  151. #print "%s %s" % (key, value)
  152. openssl_macro_by_hex[key] = value
  153. all_openssl_macros[value]=key
  154. fp.close()
  155. # Now generate the output.
  156. print("""\
  157. /* This is an include file used to define the list of ciphers clients should
  158. * advertise. Before including it, you should define the CIPHER and XCIPHER
  159. * macros.
  160. *
  161. * This file was automatically generated by get_mozilla_ciphers.py.
  162. */""")
  163. # Go in order by the order in CipherPrefs
  164. for firefox_macro in firefox_ciphers:
  165. try:
  166. js_cipher_name = cipher_pref[firefox_macro]
  167. except KeyError:
  168. # This one has no javascript preference.
  169. continue
  170. # The cipher needs to be enabled in security-prefs.js
  171. if enabled_ciphers.get(js_cipher_name, 'false') != 'true':
  172. continue
  173. hexval = sslProtoD[firefox_macro].lower()
  174. try:
  175. openssl_macro = openssl_macro_by_hex[hexval.lower()]
  176. openssl_macro = openssl_macro.replace("_CK_", "_TXT_")
  177. if openssl_macro not in all_openssl_macros:
  178. raise KeyError()
  179. format = {'hex':hexval, 'macro':openssl_macro, 'note':""}
  180. except KeyError:
  181. # openssl doesn't have a macro for this.
  182. format = {'hex':hexval, 'macro':firefox_macro,
  183. 'note':"/* No openssl macro found for "+hexval+" */\n"}
  184. res = """\
  185. %(note)s#ifdef %(macro)s
  186. CIPHER(%(hex)s, %(macro)s)
  187. #else
  188. XCIPHER(%(hex)s, %(macro)s)
  189. #endif""" % format
  190. print(res)