get_mozilla_ciphers.py 5.6 KB

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