hs_ntor_ref.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. #!/usr/bin/python
  2. # Copyright 2017, The Tor Project, Inc
  3. # See LICENSE for licensing information
  4. """
  5. hs_ntor_ref.py
  6. This module is a reference implementation of the modified ntor protocol
  7. proposed for Tor hidden services in proposal 224 (Next Generation Hidden
  8. Services) in section [NTOR-WITH-EXTRA-DATA].
  9. The modified ntor protocol is a single-round protocol, with three steps in total:
  10. 1: Client generates keys and sends them to service via INTRODUCE cell
  11. 2: Service computes key material based on client's keys, and sends its own
  12. keys to client via RENDEZVOUS cell
  13. 3: Client computes key material as well.
  14. It's meant to be used to validate Tor's HS ntor implementation by conducting
  15. various integration tests. Specifically it conducts the following three tests:
  16. - Tests our Python implementation by running the whole protocol in Python and
  17. making sure that results are consistent.
  18. - Tests little-t-tor ntor implementation. We use this Python code to instrument
  19. little-t-tor and carry out the handshake by using little-t-tor code. The
  20. small C wrapper at src/test/test-hs-ntor-cl is used for this Python module to
  21. interface with little-t-tor.
  22. - Cross-tests Python and little-t-tor implementation by running half of the
  23. protocol in Python code and the other in little-t-tor. This is actually two
  24. tests so that all parts of the protocol are run both by little-t-tor and
  25. Python.
  26. It requires the curve25519 python module from the curve25519-donna package.
  27. The whole logic and concept for this test suite was taken from ntor_ref.py.
  28. *** DO NOT USE THIS IN PRODUCTION. ***
  29. """
  30. import struct
  31. import os, sys
  32. import binascii
  33. import subprocess
  34. try:
  35. import curve25519
  36. curve25519mod = curve25519.keys
  37. except ImportError:
  38. curve25519 = None
  39. import slownacl_curve25519
  40. curve25519mod = slownacl_curve25519
  41. try:
  42. import sha3
  43. except ImportError:
  44. # error code 77 tells automake to skip this test
  45. sys.exit(77)
  46. # Import Nick's ntor reference implementation in Python
  47. # We are gonna use a few of its utilities.
  48. from ntor_ref import hash_nil
  49. from ntor_ref import PrivateKey
  50. # String constants used in this protocol
  51. PROTOID = "tor-hs-ntor-curve25519-sha3-256-1"
  52. T_HSENC = PROTOID + ":hs_key_extract"
  53. T_HSVERIFY = PROTOID + ":hs_verify"
  54. T_HSMAC = PROTOID + ":hs_mac"
  55. M_HSEXPAND = PROTOID + ":hs_key_expand"
  56. INTRO_SECRET_LEN = 161
  57. REND_SECRET_LEN = 225
  58. AUTH_INPUT_LEN = 199
  59. # Implements MAC(k,m) = H(htonll(len(k)) | k | m)
  60. def mac(k,m):
  61. def htonll(num):
  62. return struct.pack('!q', num)
  63. s = sha3.SHA3256()
  64. s.update(htonll(len(k)))
  65. s.update(k)
  66. s.update(m)
  67. return s.digest()
  68. ######################################################################
  69. # Functions that implement the modified HS ntor protocol
  70. """As client compute key material for INTRODUCE cell as follows:
  71. intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID
  72. info = m_hsexpand | subcredential
  73. hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
  74. ENC_KEY = hs_keys[0:S_KEY_LEN]
  75. MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
  76. """
  77. def intro2_ntor_client(intro_auth_pubkey_str, intro_enc_pubkey,
  78. client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential):
  79. dh_result = client_ephemeral_enc_privkey.get_shared_key(intro_enc_pubkey, hash_nil)
  80. secret = dh_result + intro_auth_pubkey_str + client_ephemeral_enc_pubkey.serialize() + intro_enc_pubkey.serialize() + PROTOID
  81. assert(len(secret) == INTRO_SECRET_LEN)
  82. info = M_HSEXPAND + subcredential
  83. kdf = sha3.SHAKE256()
  84. kdf.update(secret + T_HSENC + info)
  85. key_material = kdf.squeeze(64*8)
  86. enc_key = key_material[0:32]
  87. mac_key = key_material[32:64]
  88. return enc_key, mac_key
  89. """Wrapper over intro2_ntor_client()"""
  90. def client_part1(intro_auth_pubkey_str, intro_enc_pubkey,
  91. client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential):
  92. enc_key, mac_key = intro2_ntor_client(intro_auth_pubkey_str, intro_enc_pubkey, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential)
  93. assert(enc_key)
  94. assert(mac_key)
  95. return enc_key, mac_key
  96. """As service compute key material for INTRODUCE cell as follows:
  97. intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID
  98. info = m_hsexpand | subcredential
  99. hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
  100. HS_DEC_KEY = hs_keys[0:S_KEY_LEN]
  101. HS_MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
  102. """
  103. def intro2_ntor_service(intro_auth_pubkey_str, client_enc_pubkey, service_enc_privkey, service_enc_pubkey, subcredential):
  104. dh_result = service_enc_privkey.get_shared_key(client_enc_pubkey, hash_nil)
  105. secret = dh_result + intro_auth_pubkey_str + client_enc_pubkey.serialize() + service_enc_pubkey.serialize() + PROTOID
  106. assert(len(secret) == INTRO_SECRET_LEN)
  107. info = M_HSEXPAND + subcredential
  108. kdf = sha3.SHAKE256()
  109. kdf.update(secret + T_HSENC + info)
  110. key_material = kdf.squeeze(64*8)
  111. enc_key = key_material[0:32]
  112. mac_key = key_material[32:64]
  113. return enc_key, mac_key
  114. """As service compute key material for INTRODUCE and REDNEZVOUS cells.
  115. Use intro2_ntor_service() to calculate the INTRODUCE key material, and use
  116. the following computations to do the RENDEZVOUS ones:
  117. rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID
  118. NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
  119. verify = MAC(rend_secret_hs_input, t_hsverify)
  120. auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
  121. AUTH_INPUT_MAC = MAC(auth_input, t_hsmac)
  122. """
  123. def service_part1(intro_auth_pubkey_str, client_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential):
  124. intro_enc_key, intro_mac_key = intro2_ntor_service(intro_auth_pubkey_str, client_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential)
  125. assert(intro_enc_key)
  126. assert(intro_mac_key)
  127. service_ephemeral_privkey = PrivateKey()
  128. service_ephemeral_pubkey = service_ephemeral_privkey.get_public()
  129. dh_result1 = service_ephemeral_privkey.get_shared_key(client_enc_pubkey, hash_nil)
  130. dh_result2 = intro_enc_privkey.get_shared_key(client_enc_pubkey, hash_nil)
  131. rend_secret_hs_input = dh_result1 + dh_result2 + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + client_enc_pubkey.serialize() + service_ephemeral_pubkey.serialize() + PROTOID
  132. assert(len(rend_secret_hs_input) == REND_SECRET_LEN)
  133. ntor_key_seed = mac(rend_secret_hs_input, T_HSENC)
  134. verify = mac(rend_secret_hs_input, T_HSVERIFY)
  135. auth_input = verify + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + service_ephemeral_pubkey.serialize() + client_enc_pubkey.serialize() + PROTOID + "Server"
  136. assert(len(auth_input) == AUTH_INPUT_LEN)
  137. auth_input_mac = mac(auth_input, T_HSMAC)
  138. assert(ntor_key_seed)
  139. assert(auth_input_mac)
  140. assert(service_ephemeral_pubkey)
  141. return intro_enc_key, intro_mac_key, ntor_key_seed, auth_input_mac, service_ephemeral_pubkey
  142. """As client compute key material for rendezvous cells as follows:
  143. rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID
  144. NTOR_KEY_SEED = MAC(ntor_secret_input, t_hsenc)
  145. verify = MAC(ntor_secret_input, t_hsverify)
  146. auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
  147. AUTH_INPUT_MAC = MAC(auth_input, t_hsmac)
  148. """
  149. def client_part2(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey,
  150. intro_enc_pubkey, service_ephemeral_rend_pubkey):
  151. dh_result1 = client_ephemeral_enc_privkey.get_shared_key(service_ephemeral_rend_pubkey, hash_nil)
  152. dh_result2 = client_ephemeral_enc_privkey.get_shared_key(intro_enc_pubkey, hash_nil)
  153. rend_secret_hs_input = dh_result1 + dh_result2 + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + client_ephemeral_enc_pubkey.serialize() + service_ephemeral_rend_pubkey.serialize() + PROTOID
  154. assert(len(rend_secret_hs_input) == REND_SECRET_LEN)
  155. ntor_key_seed = mac(rend_secret_hs_input, T_HSENC)
  156. verify = mac(rend_secret_hs_input, T_HSVERIFY)
  157. auth_input = verify + intro_auth_pubkey_str + intro_enc_pubkey.serialize() + service_ephemeral_rend_pubkey.serialize() + client_ephemeral_enc_pubkey.serialize() + PROTOID + "Server"
  158. assert(len(auth_input) == AUTH_INPUT_LEN)
  159. auth_input_mac = mac(auth_input, T_HSMAC)
  160. assert(ntor_key_seed)
  161. assert(auth_input_mac)
  162. return ntor_key_seed, auth_input_mac
  163. #################################################################################
  164. """
  165. Utilities for communicating with the little-t-tor ntor wrapper to conduct the
  166. integration tests
  167. """
  168. PROG = b"./src/test/test-hs-ntor-cl"
  169. enhex=lambda s: binascii.b2a_hex(s)
  170. dehex=lambda s: binascii.a2b_hex(s.strip())
  171. def tor_client1(intro_auth_pubkey_str, intro_enc_pubkey,
  172. client_ephemeral_enc_privkey, subcredential):
  173. p = subprocess.Popen([PROG, "client1",
  174. enhex(intro_auth_pubkey_str),
  175. enhex(intro_enc_pubkey.serialize()),
  176. enhex(client_ephemeral_enc_privkey.serialize()),
  177. enhex(subcredential)],
  178. stdout=subprocess.PIPE)
  179. return map(dehex, p.stdout.readlines())
  180. def tor_server1(intro_auth_pubkey_str, intro_enc_privkey,
  181. client_ephemeral_enc_pubkey, subcredential):
  182. p = subprocess.Popen([PROG, "server1",
  183. enhex(intro_auth_pubkey_str),
  184. enhex(intro_enc_privkey.serialize()),
  185. enhex(client_ephemeral_enc_pubkey.serialize()),
  186. enhex(subcredential)],
  187. stdout=subprocess.PIPE)
  188. return map(dehex, p.stdout.readlines())
  189. def tor_client2(intro_auth_pubkey_str, client_ephemeral_enc_privkey,
  190. intro_enc_pubkey, service_ephemeral_rend_pubkey, subcredential):
  191. p = subprocess.Popen([PROG, "client2",
  192. enhex(intro_auth_pubkey_str),
  193. enhex(client_ephemeral_enc_privkey.serialize()),
  194. enhex(intro_enc_pubkey.serialize()),
  195. enhex(service_ephemeral_rend_pubkey.serialize()),
  196. enhex(subcredential)],
  197. stdout=subprocess.PIPE)
  198. return map(dehex, p.stdout.readlines())
  199. ##################################################################################
  200. # Perform a pure python ntor test
  201. def do_pure_python_ntor_test():
  202. # Initialize all needed key material
  203. client_ephemeral_enc_privkey = PrivateKey()
  204. client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public()
  205. intro_enc_privkey = PrivateKey()
  206. intro_enc_pubkey = intro_enc_privkey.get_public()
  207. intro_auth_pubkey_str = os.urandom(32)
  208. subcredential = os.urandom(32)
  209. client_enc_key, client_mac_key = client_part1(intro_auth_pubkey_str, intro_enc_pubkey, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey, subcredential)
  210. service_enc_key, service_mac_key, service_ntor_key_seed, service_auth_input_mac, service_ephemeral_pubkey = service_part1(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential)
  211. assert(client_enc_key == service_enc_key)
  212. assert(client_mac_key == service_mac_key)
  213. client_ntor_key_seed, client_auth_input_mac = client_part2(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey,
  214. intro_enc_pubkey, service_ephemeral_pubkey)
  215. assert(client_ntor_key_seed == service_ntor_key_seed)
  216. assert(client_auth_input_mac == service_auth_input_mac)
  217. print "DONE: python dance [%s]" % repr(client_auth_input_mac)
  218. # Perform a pure little-t-tor integration test.
  219. def do_little_t_tor_ntor_test():
  220. # Initialize all needed key material
  221. subcredential = os.urandom(32)
  222. client_ephemeral_enc_privkey = PrivateKey()
  223. client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public()
  224. intro_enc_privkey = PrivateKey()
  225. intro_enc_pubkey = intro_enc_privkey.get_public() # service-side enc key
  226. intro_auth_pubkey_str = os.urandom(32)
  227. client_enc_key, client_mac_key = tor_client1(intro_auth_pubkey_str, intro_enc_pubkey,
  228. client_ephemeral_enc_privkey, subcredential)
  229. assert(client_enc_key)
  230. assert(client_mac_key)
  231. service_enc_key, service_mac_key, service_ntor_auth_mac, service_ntor_key_seed, service_eph_pubkey = tor_server1(intro_auth_pubkey_str,
  232. intro_enc_privkey,
  233. client_ephemeral_enc_pubkey,
  234. subcredential)
  235. assert(service_enc_key)
  236. assert(service_mac_key)
  237. assert(service_ntor_auth_mac)
  238. assert(service_ntor_key_seed)
  239. assert(client_enc_key == service_enc_key)
  240. assert(client_mac_key == service_mac_key)
  241. # Turn from bytes to key
  242. service_eph_pubkey = curve25519mod.Public(service_eph_pubkey)
  243. client_ntor_auth_mac, client_ntor_key_seed = tor_client2(intro_auth_pubkey_str, client_ephemeral_enc_privkey,
  244. intro_enc_pubkey, service_eph_pubkey, subcredential)
  245. assert(client_ntor_auth_mac)
  246. assert(client_ntor_key_seed)
  247. assert(client_ntor_key_seed == service_ntor_key_seed)
  248. assert(client_ntor_auth_mac == service_ntor_auth_mac)
  249. print "DONE: tor dance [%s]" % repr(client_ntor_auth_mac)
  250. """
  251. Do mixed test as follows:
  252. 1. C -> S (python mode)
  253. 2. C <- S (tor mode)
  254. 3. Client computes keys (python mode)
  255. """
  256. def do_first_mixed_test():
  257. subcredential = os.urandom(32)
  258. client_ephemeral_enc_privkey = PrivateKey()
  259. client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public()
  260. intro_enc_privkey = PrivateKey()
  261. intro_enc_pubkey = intro_enc_privkey.get_public() # service-side enc key
  262. intro_auth_pubkey_str = os.urandom(32)
  263. # Let's do mixed
  264. client_enc_key, client_mac_key = client_part1(intro_auth_pubkey_str, intro_enc_pubkey,
  265. client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey,
  266. subcredential)
  267. service_enc_key, service_mac_key, service_ntor_auth_mac, service_ntor_key_seed, service_eph_pubkey = tor_server1(intro_auth_pubkey_str,
  268. intro_enc_privkey,
  269. client_ephemeral_enc_pubkey,
  270. subcredential)
  271. assert(service_enc_key)
  272. assert(service_mac_key)
  273. assert(service_ntor_auth_mac)
  274. assert(service_ntor_key_seed)
  275. assert(service_eph_pubkey)
  276. assert(client_enc_key == service_enc_key)
  277. assert(client_mac_key == service_mac_key)
  278. # Turn from bytes to key
  279. service_eph_pubkey = curve25519mod.Public(service_eph_pubkey)
  280. client_ntor_key_seed, client_auth_input_mac = client_part2(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, client_ephemeral_enc_privkey,
  281. intro_enc_pubkey, service_eph_pubkey)
  282. assert(client_auth_input_mac == service_ntor_auth_mac)
  283. assert(client_ntor_key_seed == service_ntor_key_seed)
  284. print "DONE: 1st mixed dance [%s]" % repr(client_auth_input_mac)
  285. """
  286. Do mixed test as follows:
  287. 1. C -> S (tor mode)
  288. 2. C <- S (python mode)
  289. 3. Client computes keys (tor mode)
  290. """
  291. def do_second_mixed_test():
  292. subcredential = os.urandom(32)
  293. client_ephemeral_enc_privkey = PrivateKey()
  294. client_ephemeral_enc_pubkey = client_ephemeral_enc_privkey.get_public()
  295. intro_enc_privkey = PrivateKey()
  296. intro_enc_pubkey = intro_enc_privkey.get_public() # service-side enc key
  297. intro_auth_pubkey_str = os.urandom(32)
  298. # Let's do mixed
  299. client_enc_key, client_mac_key = tor_client1(intro_auth_pubkey_str, intro_enc_pubkey,
  300. client_ephemeral_enc_privkey, subcredential)
  301. assert(client_enc_key)
  302. assert(client_mac_key)
  303. service_enc_key, service_mac_key, service_ntor_key_seed, service_ntor_auth_mac, service_ephemeral_pubkey = service_part1(intro_auth_pubkey_str, client_ephemeral_enc_pubkey, intro_enc_privkey, intro_enc_pubkey, subcredential)
  304. client_ntor_auth_mac, client_ntor_key_seed = tor_client2(intro_auth_pubkey_str, client_ephemeral_enc_privkey,
  305. intro_enc_pubkey, service_ephemeral_pubkey, subcredential)
  306. assert(client_ntor_auth_mac)
  307. assert(client_ntor_key_seed)
  308. assert(client_ntor_key_seed == service_ntor_key_seed)
  309. assert(client_ntor_auth_mac == service_ntor_auth_mac)
  310. print "DONE: 2nd mixed dance [%s]" % repr(client_ntor_auth_mac)
  311. def do_mixed_tests():
  312. do_first_mixed_test()
  313. do_second_mixed_test()
  314. if __name__ == '__main__':
  315. do_pure_python_ntor_test()
  316. do_little_t_tor_ntor_test()
  317. do_mixed_tests()