relay.py 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. #!/usr/bin/env python3
  2. import random # For simulation, not cryptography!
  3. import math
  4. import sys
  5. import nacl.utils
  6. import nacl.signing
  7. import nacl.public
  8. import nacl.hash
  9. import nacl.bindings
  10. import network
  11. import dirauth
  12. class RelayNetMsg(network.NetMsg):
  13. """The subclass of NetMsg for messages between relays and either
  14. relays or clients."""
  15. class RelayGetConsensusMsg(RelayNetMsg):
  16. """The subclass of RelayNetMsg for fetching the consensus. Sent by
  17. clients to relays."""
  18. class RelayConsensusMsg(RelayNetMsg):
  19. """The subclass of RelayNetMsg for returning the consensus from
  20. relays to clients, in response to RelayGetConsensusMsg."""
  21. def __init__(self, consensus):
  22. self.consensus = consensus
  23. class RelayGetConsensusDiffMsg(RelayNetMsg):
  24. """The subclass of RelayNetMsg for fetching the consensus, if the
  25. requestor already has the previous consensus. Sent by clients to
  26. relays."""
  27. class RelayConsensusDiffMsg(RelayNetMsg):
  28. """The subclass of RelayNetMsg for returning the consensus, if the
  29. requestor already has the previous consensus. We don't _actually_
  30. produce the diff at this time; we just charge fewer bytes for this
  31. message. Sent by relays to clients in response to
  32. RelayGetConsensusDiffMsg."""
  33. def __init__(self, consensus):
  34. self.consensus = consensus
  35. def size(self):
  36. if network.symbolic_byte_counters:
  37. return super().size()
  38. return math.ceil(RelayConsensusMsg(self.consensus).size() \
  39. * network.P_Delta)
  40. class RelayRandomHopMsg(RelayNetMsg):
  41. """A message used for testing, that hops from relay to relay
  42. randomly until its TTL expires."""
  43. def __init__(self, ttl):
  44. self.ttl = ttl
  45. def __str__(self):
  46. return "RandomHop TTL=%d" % self.ttl
  47. class CircuitCellMsg(RelayNetMsg):
  48. """Send a message tagged with a circuit id. This is the container
  49. class for all RelayCell messages."""
  50. def __init__(self, circuitid, cell):
  51. self.circid = circuitid
  52. self.cell = cell
  53. def __str__(self):
  54. return "C%d:%s" % (self.circid, self.cell)
  55. def size(self):
  56. # circuitids are 4 bytes
  57. return 4 + self.cell.size()
  58. class RelayCell(RelayNetMsg):
  59. """All cells (which are sent inside a CircuitCellMsg, and so do not
  60. need their own circuitid) should be a subclass of this class."""
  61. class StringCell(RelayCell):
  62. """Send an arbitrary string as a cell."""
  63. def __init__(self, str):
  64. self.data = str
  65. def __str__(self):
  66. return self.data.__str__()
  67. class CloseCell(RelayCell):
  68. """Close the circuit this cell was sent on. It should be sent
  69. _unencrypted_ (not within an EncryptedCell), and relays that receive
  70. one should forward it along the adjacent circuit, then close both
  71. the circuit it was received on and the adjacent one."""
  72. # It is intentional that VanillaCreateCircuitMsg is a RelayNetMsg and
  73. # not a RelayCell. This is the message that _creates_ the circuit, so
  74. # it can't be sent as a cell _within_ the circuit.
  75. class VanillaCreateCircuitMsg(RelayNetMsg):
  76. """The message for requesting circuit creation in Vanilla Onion
  77. Routing."""
  78. def __init__(self, circid, ntor_request):
  79. self.circid = circid
  80. self.ntor_request = ntor_request
  81. class VanillaCreatedCircuitCell(RelayCell):
  82. """The message for responding to circuit creation in Vanilla Onion
  83. Routing."""
  84. def __init__(self, ntor_reply):
  85. self.ntor_reply = ntor_reply
  86. class VanillaExtendCircuitCell(RelayCell):
  87. """The message for requesting circuit extension in Vanilla Onion
  88. Routing."""
  89. def __init__(self, hopaddr, ntor_request):
  90. self.hopaddr = hopaddr
  91. self.ntor_request = ntor_request
  92. class VanillaExtendedCircuitCell(RelayCell):
  93. """The message for responding to circuit extension in Vanilla Onion
  94. Routing."""
  95. def __init__(self, ntor_reply):
  96. self.ntor_reply = ntor_reply
  97. # It is intentional that TelescopingCreateCircuitMsg is a RelayNetMsg and
  98. # not a RelayCell. This is the message that _creates_ the circuit, so
  99. # it can't be sent as a cell _within_ the circuit.
  100. class TelescopingCreateCircuitMsg(RelayNetMsg):
  101. """The message for requesting circuit creation in Telescoping Onion
  102. Routing."""
  103. def __init__(self, circid, ntor_request):
  104. self.circid = circid
  105. self.ntor_request = ntor_request
  106. class TelescopingCreatedCircuitCell(RelayCell):
  107. """The message for responding to circuit creation in Telescoping Walking
  108. Onions."""
  109. def __init__(self, ntor_reply):
  110. self.ntor_reply = ntor_reply
  111. class TelescopingExtendCircuitCell(RelayCell):
  112. """The message for requesting circuit extension in Telescoping Walking
  113. Onionss."""
  114. def __init__(self, idx, ntor_request):
  115. self.idx = idx
  116. self.ntor_request = ntor_request
  117. class TelescopingExtendedCircuitCell(RelayCell):
  118. """The message for responding to circuit extension in Telescoping Walking
  119. Onions."""
  120. def __init__(self, ntor_reply, snip):
  121. self.ntor_reply = ntor_reply
  122. self.snip = snip
  123. class EncryptedCell(RelayCell):
  124. """Send a message encrypted with a symmetric key. In this
  125. implementation, the encryption is not really done. A hash of the
  126. key is stored with the message so that it can be checked at
  127. decryption time."""
  128. def __init__(self, key, msg):
  129. self.keyhash = nacl.hash.sha256(key)
  130. self.plaintext = msg
  131. def decrypt(self, key):
  132. keyhash = nacl.hash.sha256(key)
  133. if keyhash != self.keyhash:
  134. raise ValueError("EncryptedCell key mismatch")
  135. return self.plaintext
  136. def size(self):
  137. # Current Tor actually has no overhead for encryption
  138. return self.plaintext.size()
  139. class RelayFallbackTerminationError(Exception):
  140. """An exception raised when someone tries to terminate a fallback
  141. relay."""
  142. class Sphinx:
  143. """Implement the public-key reblinding technique based on Sphinx.
  144. This does a few more public-key operations than it would strictly
  145. need to if we were using a group implementation that (unlike nacl)
  146. supported the operations we needed directly. The biggest issue is
  147. that nacl insists the high bit is set on private keys, which means
  148. we can't just multiply private keys together to get a new private
  149. key, and do a single DH operation with that resulting key; we have
  150. to perform a linear number of DH operations instead, per node in the
  151. circuit, so a quadratic number of DH operations total."""
  152. @staticmethod
  153. def makeblindkey(shared_secret, domain_separator, perfstats):
  154. """Create a Sphinx reblinding key (a PrivateKey) out of a shared
  155. secret and a domain separator (both bytestrings). The domain
  156. separator is just a constant bytestring like b'data' or
  157. b'circuit' for the data-protecting and circuit-protecting
  158. public-key elements respectively."""
  159. rawkey = nacl.hash.sha256(domain_separator + shared_secret,
  160. encoder=nacl.encoding.RawEncoder)
  161. perfstats.keygens += 1
  162. # The PrivateKey constructor does the Curve25519 pinning of
  163. # certain bits of the key to 0 and 1
  164. return nacl.public.PrivateKey(rawkey)
  165. @staticmethod
  166. def reblindpubkey(blindkey, pubkey, perfstats):
  167. """Create a Sphinx reblinded PublicKey out of a reblinding key
  168. (output by makeblindkey) and a (possibly already reblinded)
  169. PublicKey."""
  170. new_pubkey = nacl.bindings.crypto_scalarmult(bytes(blindkey),
  171. bytes(pubkey))
  172. perfstats.dhs += 1
  173. return nacl.public.PublicKey(new_pubkey)
  174. @staticmethod
  175. def client(client_privkey, blindkey_list, server_pubkey,
  176. domain_separator, is_last, perfstats):
  177. """Given the client's PrivateKey, a (possibly empty) list of
  178. reblinding keys, and the server's PublicKey, produce the shared
  179. secret and the new blinding key (to add to the list). The
  180. domain separator is as above. If is_last is true, don't bother
  181. creating the new blinding key, since this is the last iteration,
  182. and we won't be using it."""
  183. reblinded_server_pubkey = server_pubkey
  184. for blindkey in blindkey_list:
  185. reblinded_server_pubkey = Sphinx.reblindpubkey(blindkey,
  186. reblinded_server_pubkey, perfstats)
  187. sharedsecret = nacl.public.Box(client_privkey,
  188. reblinded_server_pubkey).shared_key()
  189. perfstats.dhs += 1
  190. if is_last:
  191. blindkey = None
  192. else:
  193. blindkey = Sphinx.makeblindkey(sharedsecret,
  194. domain_separator, perfstats)
  195. return sharedsecret, blindkey
  196. @staticmethod
  197. def server(client_pubkey, server_privkey, domain_separator, is_last,
  198. perfstats):
  199. """Given the client's PublicKey and the server's PrivateKey,
  200. produce the shared secret and the new reblinded client
  201. PublicKey. The domain separator is as above. If is_last is
  202. True, don't bother generating the new PublicKey, since we're the
  203. last server in the chain, and won't be using it."""
  204. sharedsecret = nacl.public.Box(server_privkey,
  205. client_pubkey).shared_key()
  206. perfstats.dhs += 1
  207. if is_last:
  208. blinded_pubkey = None
  209. else:
  210. blindkey = Sphinx.makeblindkey(sharedsecret, domain_separator,
  211. perfstats)
  212. blinded_pubkey = Sphinx.reblindpubkey(blindkey, client_pubkey,
  213. perfstats)
  214. return sharedsecret, blinded_pubkey
  215. class NTor:
  216. """A class implementing the ntor one-way authenticated key agreement
  217. scheme. The details are not exactly the same as either the ntor
  218. paper or Tor's implementation, but it will agree on keys and have
  219. the same number of public key operations."""
  220. def __init__(self, perfstats):
  221. self.perfstats = perfstats
  222. # Only used for Single-Pass Walking Onions; it is the sequence
  223. # of blinding keys used by Sphinx
  224. self.blinding_keys = []
  225. def request(self):
  226. """Create the ntor request message: X = g^x."""
  227. self.client_ephem_key = nacl.public.PrivateKey.generate()
  228. self.perfstats.keygens += 1
  229. return self.client_ephem_key.public_key
  230. @staticmethod
  231. def reply(onion_privkey, idpubkey, client_pubkey, perfstats,
  232. sphinx_domainsep=None):
  233. """The server calls this static method to produce the ntor reply
  234. message: (Y = g^y, B = g^b, A = H(M, "verify")) and the shared
  235. secret S = H(M, "secret") for M = (X^y,X^b,ID,B,X,Y). If
  236. sphinx_domainsep is not None, also compute and return the Sphinx
  237. reblinded client request to pass to the next server."""
  238. server_ephem_key = nacl.public.PrivateKey.generate()
  239. perfstats.keygens += 1
  240. xykey = nacl.public.Box(server_ephem_key, client_pubkey).shared_key()
  241. xbkey = nacl.public.Box(onion_privkey, client_pubkey).shared_key()
  242. perfstats.dhs += 2
  243. M = xykey + xbkey + \
  244. idpubkey.encode(encoder=nacl.encoding.RawEncoder) + \
  245. onion_privkey.public_key.encode(encoder=nacl.encoding.RawEncoder) + \
  246. server_ephem_key.public_key.encode(encoder=nacl.encoding.RawEncoder)
  247. A = nacl.hash.sha256(M + b'verify', encoder=nacl.encoding.RawEncoder)
  248. S = nacl.hash.sha256(M + b'secret', encoder=nacl.encoding.RawEncoder)
  249. if sphinx_domainsep is not None:
  250. blindkey = Sphinx.makeblindkey(S, sphinx_domainsep, perfstats)
  251. blinded_client_pubkey = Sphinx.reblindpubkey(blindkey,
  252. client_pubkey, perfstats)
  253. return ((server_ephem_key.public_key, onion_privkey.public_key, A),
  254. S), blinded_client_pubkey
  255. else:
  256. return ((server_ephem_key.public_key, onion_privkey.public_key, A),
  257. S)
  258. def verify(self, reply, onion_pubkey, idpubkey, sphinx_domainsep=None):
  259. """The client calls this method to verify the ntor reply
  260. message, passing the onion and id public keys for the server
  261. it's expecting to be talking to. If sphinx_domainsep is not
  262. None, also compute the reblinding key so that the client can
  263. reuse this same NTor object for the next server. Returns the
  264. shared secret on success, or raises ValueError on failure."""
  265. server_ephem_pubkey, server_onion_pubkey, authtag = reply
  266. if onion_pubkey != server_onion_pubkey:
  267. raise ValueError("NTor onion pubkey mismatch")
  268. # We use the blinding keys if present; if they're not present
  269. # (because we're not in Single-Pass Walking Onions), the loops
  270. # are just empty anyway, so everything will work in the usual
  271. # unblinded way.
  272. reblinded_server_ephem_pubkey = server_ephem_pubkey
  273. for blindkey in self.blinding_keys:
  274. reblinded_server_ephem_pubkey = Sphinx.reblindpubkey(blindkey,
  275. reblinded_server_ephem_pubkey, self.perfstats)
  276. xykey = nacl.public.Box(self.client_ephem_key,
  277. reblinded_server_ephem_pubkey).shared_key()
  278. reblinded_onion_pubkey = onion_pubkey
  279. for blindkey in self.blinding_keys:
  280. reblinded_onion_pubkey = Sphinx.reblindpubkey(blindkey,
  281. reblinded_onion_pubkey, self.perfstats)
  282. xbkey = nacl.public.Box(self.client_ephem_key,
  283. reblinded_onion_pubkey).shared_key()
  284. self.perfstats.dhs += 2
  285. M = xykey + xbkey + \
  286. idpubkey.encode(encoder=nacl.encoding.RawEncoder) + \
  287. onion_pubkey.encode(encoder=nacl.encoding.RawEncoder) + \
  288. server_ephem_pubkey.encode(encoder=nacl.encoding.RawEncoder)
  289. Acheck = nacl.hash.sha256(M + b'verify', encoder=nacl.encoding.RawEncoder)
  290. S = nacl.hash.sha256(M + b'secret', encoder=nacl.encoding.RawEncoder)
  291. if Acheck != authtag:
  292. raise ValueError("NTor auth mismatch")
  293. if sphinx_domainsep is not None:
  294. blindkey = Sphinx.makeblindkey(S, sphinx_domainsep,
  295. self.perfstats)
  296. self.blinding_keys.append(blindkey)
  297. return S
  298. class VanillaExtendCircuitHandler:
  299. """A handler for VanillaExtendCircuitCell cells. It allocates a new
  300. circuit id on the Channel to the requested next hop, connects the
  301. existing and new circuits together, and forwards a
  302. VanillaCreateCircuitMsg to the next hop."""
  303. def received_cell(self, circhandler, cell):
  304. # Remove ourselves from handling a second
  305. # VanillaExtendCircuitCell on this circuit
  306. circhandler.replace_celltype_handler(VanillaExtendCircuitCell, None)
  307. # Allocate a new circuit id to the requested next hop
  308. channelmgr = circhandler.channel.channelmgr
  309. nexthopchannel = channelmgr.get_channel_to(cell.hopaddr)
  310. newcircid, newcirchandler = nexthopchannel.new_circuit()
  311. # Connect the existing and new circuits together
  312. circhandler.adjacent_circuit_handler = newcirchandler
  313. newcirchandler.adjacent_circuit_handler = circhandler
  314. # Set up a handler for when the VanillaCreatedCircuitCell comes
  315. # back
  316. newcirchandler.replace_celltype_handler(
  317. VanillaCreatedCircuitCell,
  318. VanillaCreatedRelayHandler())
  319. # Forward a VanillaCreateCircuitMsg to the next hop
  320. nexthopchannel.send_msg(
  321. VanillaCreateCircuitMsg(newcircid, cell.ntor_request))
  322. class TelescopingExtendCircuitHandler:
  323. """A handler for TelescopingExtendCircuitCell cells. It allocates a new
  324. circuit id on the Channel to the requested next hop, connects the
  325. existing and new circuits together, and forwards a
  326. TelescopingCreateCircuitMsg to the next hop."""
  327. def __init__(self, relaypicker, current_relay_idkey):
  328. self.relaypicker = relaypicker
  329. self.current_relay_idkey = current_relay_idkey
  330. def received_cell(self, circhandler, cell):
  331. # Remove ourselves from handling a second
  332. # TelescopingExtendCircuitCell on this circuit
  333. circhandler.replace_celltype_handler(TelescopingExtendCircuitCell, None)
  334. # Find the SNIP corresponding to the index sent by the client
  335. next_snip = self.relaypicker.pick_relay_by_uniform_index(cell.idx)
  336. # Check to make sure that we aren't extending to ourselves. If we are,
  337. # close the circuit.
  338. if next_snip.snipdict["idkey"] == self.current_relay_idkey:
  339. print("ERR: Client requested extending the circuit to a relay already in the path; aborting. my circid: " + str(circhandler.circid))
  340. circhandler.close()
  341. return
  342. # Allocate a new circuit id to the requested next hop
  343. channelmgr = circhandler.channel.channelmgr
  344. nexthopchannel = channelmgr.get_channel_to(next_snip.snipdict["addr"])
  345. newcircid, newcirchandler = nexthopchannel.new_circuit()
  346. # Connect the existing and new circuits together
  347. circhandler.adjacent_circuit_handler = newcirchandler
  348. newcirchandler.adjacent_circuit_handler = circhandler
  349. # Set up a handler for when the TelescopingCreatedCircuitCell comes
  350. # back
  351. newcirchandler.replace_celltype_handler(
  352. TelescopingCreatedCircuitCell,
  353. TelescopingCreatedRelayHandler(next_snip))
  354. # Forward a TelescopingCreateCircuitMsg to the next hop
  355. nexthopchannel.send_msg(
  356. TelescopingCreateCircuitMsg(newcircid, cell.ntor_request))
  357. class VanillaCreatedRelayHandler:
  358. """Handle a VanillaCreatedCircuitCell received by a _relay_ that
  359. recently received a VanillaExtendCircuitCell from a client, and so
  360. forwarded a VanillaCreateCircuitCell to the next hop."""
  361. def received_cell(self, circhandler, cell):
  362. # Remove ourselves from handling a second
  363. # VanillaCreatedCircuitCell on this circuit
  364. circhandler.replace_celltype_handler(VanillaCreatedCircuitCell, None)
  365. # Just forward a VanillaExtendedCircuitCell back towards the
  366. # client
  367. circhandler.adjacent_circuit_handler.send_cell(
  368. VanillaExtendedCircuitCell(cell.ntor_reply))
  369. class TelescopingCreatedRelayHandler:
  370. """Handle a TelescopingCreatedCircuitCell received by a _relay_ that
  371. recently received a TelescopingExtendCircuitCell from a client, and so
  372. forwarded a TelescopingCreateCircuitCell to the next hop."""
  373. def __init__(self, next_snip):
  374. self.next_snip = next_snip
  375. def received_cell(self, circhandler, cell):
  376. print("LOG: Handle a TelescopingCreatedCircui received by a relay")
  377. # Remove ourselves from handling a second
  378. # VanillaCreatedCircuitCell on this circuit
  379. circhandler.replace_celltype_handler(TelescopingCreatedCircuitCell, None)
  380. # Just forward a TelescopingExtendedCircuitCell back towards the
  381. # client
  382. circhandler.adjacent_circuit_handler.send_cell(
  383. TelescopingExtendedCircuitCell(cell.ntor_reply, self.next_snip))
  384. class CircuitHandler:
  385. """A class for managing sending and receiving encrypted cells on a
  386. particular circuit."""
  387. class NoCryptLayer:
  388. def encrypt_msg(self, msg): return msg
  389. def decrypt_msg(self, msg): return msg
  390. class CryptLayer:
  391. def __init__(self, enckey, deckey, next_layer):
  392. self.enckey = enckey
  393. self.deckey = deckey
  394. self.next_layer = next_layer
  395. def encrypt_msg(self, msg):
  396. return self.next_layer.encrypt_msg(EncryptedCell(self.enckey, msg))
  397. def decrypt_msg(self, msg):
  398. return self.next_layer.decrypt_msg(msg).decrypt(self.deckey)
  399. def __init__(self, channel, circid):
  400. self.channel = channel
  401. self.circid = circid
  402. # The list of relay descriptors that form the circuit so far
  403. # (client side only)
  404. self.circuit_descs = []
  405. # The dispatch table is indexed by type, and the values are
  406. # objects with received_cell(circhandler, cell) methods.
  407. self.cell_dispatch_table = dict()
  408. # The topmost crypt layer. This is an object with
  409. # encrypt_msg(msg) and decrypt_msg(msg) methods that returns the
  410. # en/decrypted messages respectively. Messages are encrypted
  411. # starting with the last layer that was added (the keys for the
  412. # furthest away relay in the circuit) and are decrypted starting
  413. # with the first layer that was added (the keys for the guard).
  414. self.crypt_layer = self.NoCryptLayer()
  415. # The adjacent CircuitHandler that's connected to this one. If
  416. # we get a cell on one, we forward it to the other (if it's not
  417. # meant for us to handle directly)
  418. self.adjacent_circuit_handler = None
  419. # The function to call when this circuit closes
  420. self.closer = lambda: self.channel.circuithandlers.pop(circid)
  421. def close(self):
  422. """Close the circuit. Sends a CloseCell on the circuit (and its
  423. adjacent circuit, if present) and closes both."""
  424. adjcirchandler = self.adjacent_circuit_handler
  425. self.adjacent_circuit_handler = None
  426. print("Log: CLosing circuit. circid: " + str(self.circid))
  427. if adjcirchandler is not None:
  428. adjcirchandler.adjacent_circuit_handler = None
  429. self.closer()
  430. self.channel_send_cell(CloseCell())
  431. if adjcirchandler is not None:
  432. adjcirchandler.closer()
  433. adjcirchandler.channel_send_cell(CloseCell())
  434. def send_cell(self, cell):
  435. """Send a cell on this circuit, encrypting as needed."""
  436. self.channel_send_cell(self.crypt_layer.encrypt_msg(cell))
  437. def channel_send_cell(self, cell):
  438. """Send a cell on this circuit directly without encrypting it
  439. first."""
  440. self.channel.send_msg(CircuitCellMsg(self.circid, cell))
  441. def received_cell(self, cell):
  442. """A cell has been received on this circuit. Dispatch it
  443. according to its type."""
  444. if isinstance(cell, EncryptedCell):
  445. cell = self.crypt_layer.decrypt_msg(cell)
  446. print("CircuitHandler: %s received cell %s on circuit %d from %s" % (self.channel.channelmgr.myaddr, cell, self.circid, self.channel.peer.channelmgr.myaddr))
  447. # If it's still encrypted, it's for sure meant for forwarding to
  448. # our adjacent hop, which had better exist.
  449. if isinstance(cell, EncryptedCell):
  450. self.adjacent_circuit_handler.send_cell(cell)
  451. else:
  452. # This is a plaintext cell meant for us. Handle it
  453. # according to the table.
  454. celltype = type(cell)
  455. if celltype in self.cell_dispatch_table:
  456. self.cell_dispatch_table[celltype].received_cell(self, cell)
  457. elif isinstance(cell, StringCell):
  458. # Default handler; just print the message in the cell
  459. print("CircuitHandler: %s received '%s' on circuit %d from %s" \
  460. % (self.channel.channelmgr.myaddr, cell,
  461. self.circid, self.channel.peer.channelmgr.myaddr))
  462. elif isinstance(cell, CloseCell):
  463. print("WARNING: Received CloseCell on circuit " + str(self.circid))
  464. # Forward the CloseCell (without encryption) to the
  465. # adjacent circuit, if any, and close both this and the
  466. # adjacent circuit
  467. adjcirchandler = self.adjacent_circuit_handler
  468. self.adjacent_circuit_handler = None
  469. if adjcirchandler is not None:
  470. adjcirchandler.adjacent_circuit_handler = None
  471. self.closer()
  472. if adjcirchandler is not None:
  473. adjcirchandler.closer()
  474. adjcirchandler.channel_send_cell(cell)
  475. else:
  476. # I don't know how to handle this cell?
  477. raise ValueError("CircuitHandler: %s received unknown cell type %s on circuit %d from %s" \
  478. % (self.channel.channelmgr.myaddr, cell,
  479. self.circid, self.channel.peer.channelmgr.myaddr))
  480. def replace_celltype_handler(self, celltype, handler):
  481. """Add an object with a received_cell(circhandler, cell) method
  482. to the cell dispatch table. It replaces anything that's already
  483. there. Passing None as the handler removes the dispatcher for
  484. that cell type."""
  485. if handler is None:
  486. del self.cell_dispatch_table[celltype]
  487. else:
  488. self.cell_dispatch_table[celltype] = handler
  489. def add_crypt_layer(self, enckey, deckey):
  490. """Add a processing layer to this CircuitHandler so that cells
  491. we send will get encrypted with the first given key, and cells
  492. we receive will be decrypted with the other given key."""
  493. current_crypt_layer = self.crypt_layer
  494. self.crypt_layer = self.CryptLayer(enckey, deckey, current_crypt_layer)
  495. class Channel(network.Connection):
  496. """A class representing a channel between a relay and either a
  497. client or a relay, transporting cells from various circuits."""
  498. def __init__(self):
  499. super().__init__()
  500. # The RelayChannelManager managing this Channel
  501. self.channelmgr = None
  502. # The Channel at the other end
  503. self.peer = None
  504. # The function to call when the connection closes
  505. self.closer = lambda: None
  506. # The next circuit id to use on this channel. The party that
  507. # opened the channel uses even numbers; the receiving party uses
  508. # odd numbers.
  509. self.next_circid = None
  510. # A map for CircuitHandlers to use for each open circuit on the
  511. # channel
  512. self.circuithandlers = dict()
  513. def closed(self):
  514. # Close each circuithandler we're managing
  515. while self.circuithandlers:
  516. chitems = iter(self.circuithandlers.items())
  517. circid, circhandler = next(chitems)
  518. print('closing circuit', circid)
  519. circhandler.close()
  520. self.closer()
  521. self.peer = None
  522. def close(self):
  523. peer = self.peer
  524. self.closed()
  525. if peer is not None and peer is not self:
  526. peer.closed()
  527. def new_circuit(self):
  528. """Allocate a new circuit on this channel, returning the new
  529. circuit's id and the new CircuitHandler."""
  530. circid = self.next_circid
  531. self.next_circid += 2
  532. circuithandler = CircuitHandler(self, circid)
  533. self.circuithandlers[circid] = circuithandler
  534. return circid, circuithandler
  535. def is_circuit_open(self, circid):
  536. is_open = (circid in self.circuithandlers) and (self.circuithandlers[circid] is not None)
  537. return is_open
  538. def new_circuit_with_circid(self, circid):
  539. """Allocate a new circuit on this channel, with the circuit id
  540. received from our peer. Return the new CircuitHandler"""
  541. circuithandler = CircuitHandler(self, circid)
  542. self.circuithandlers[circid] = circuithandler
  543. return circuithandler
  544. def send_cell(self, circid, cell):
  545. """Send the given message on the given circuit, encrypting or
  546. decrypting as needed."""
  547. self.circuithandlers[circid].send_cell(cell)
  548. def send_raw_cell(self, circid, cell):
  549. """Send the given message, tagged for the given circuit id. No
  550. encryption or decryption is done."""
  551. self.send_msg(CircuitCellMsg(self.circid, self.cell))
  552. def send_msg(self, msg):
  553. """Send the given NetMsg on the channel."""
  554. self.channelmgr.perfstats.bytes_sent += msg.size()
  555. self.peer.received(self.channelmgr.myaddr, msg)
  556. def received(self, peeraddr, msg):
  557. """Callback when a message is received from the network."""
  558. print('Channel: %s received %s from %s' % (self.channelmgr.myaddr, msg, peeraddr))
  559. self.channelmgr.perfstats.bytes_received += msg.size()
  560. if isinstance(msg, CircuitCellMsg):
  561. circid, cell = msg.circid, msg.cell
  562. self.circuithandlers[circid].received_cell(cell)
  563. else:
  564. self.channelmgr.received_msg(msg, peeraddr, self)
  565. class ChannelManager:
  566. """The class that manages the channels to other relays and clients.
  567. Relays and clients both use subclasses of this class to both create
  568. on-demand channels to relays, to gracefully handle the closing of
  569. channels, and to handle commands received over the channels."""
  570. def __init__(self, myaddr, dirauthaddrs, perfstats):
  571. # A dictionary of Channels to other hosts, indexed by NetAddr
  572. self.channels = dict()
  573. self.myaddr = myaddr
  574. self.dirauthaddrs = dirauthaddrs
  575. self.consensus = None
  576. self.relaypicker = None
  577. self.perfstats = perfstats
  578. def terminate(self):
  579. """Close all connections we're managing."""
  580. while self.channels:
  581. channelitems = iter(self.channels.items())
  582. addr, channel = next(channelitems)
  583. print('closing channel', addr, channel)
  584. channel.close()
  585. def add_channel(self, channel, peeraddr):
  586. """Add the given channel to the list of channels we are
  587. managing. If we are already managing a channel to the same
  588. peer, close it first."""
  589. if peeraddr in self.channels:
  590. self.channels[peeraddr].close()
  591. channel.channelmgr = self
  592. self.channels[peeraddr] = channel
  593. channel.closer = lambda: self.channels.pop(peeraddr)
  594. def get_channel_to(self, addr):
  595. """Get the Channel connected to the given NetAddr, creating one
  596. if none exists right now."""
  597. if addr in self.channels:
  598. return self.channels[addr]
  599. # Create the new channel
  600. print('getting channel from',self.myaddr,'to',addr)
  601. newchannel = network.thenetwork.connect(self.myaddr, addr,
  602. self.perfstats)
  603. print('got channel from',self.myaddr,'to',addr)
  604. self.channels[addr] = newchannel
  605. newchannel.closer = lambda: self.channels.pop(addr)
  606. newchannel.channelmgr = self
  607. return newchannel
  608. def received_msg(self, msg, peeraddr, channel):
  609. """Callback when a NetMsg not specific to a circuit is
  610. received."""
  611. print("ChannelManager: Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  612. def received_cell(self, circid, cell, peeraddr, channel):
  613. """Callback with a circuit-specific cell is received."""
  614. print("ChannelManager: Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  615. def send_msg(self, msg, peeraddr):
  616. """Send a message to the peer with the given address."""
  617. channel = self.get_channel_to(peeraddr)
  618. channel.send_msg(msg)
  619. def send_cell(self, circid, cell, peeraddr):
  620. """Send a cell on the given circuit to the peer with the given
  621. address."""
  622. channel = self.get_channel_to(peeraddr)
  623. channel.send_cell(circid, cell)
  624. class RelayChannelManager(ChannelManager):
  625. """The subclass of ChannelManager for relays."""
  626. def __init__(self, myaddr, dirauthaddrs, onionprivkey, idpubkey, perfstats):
  627. super().__init__(myaddr, dirauthaddrs, perfstats)
  628. self.onionkey = onionprivkey
  629. self.idpubkey = idpubkey
  630. if network.thenetwork.womode != network.WOMode.VANILLA:
  631. self.endive = None
  632. def get_consensus(self):
  633. """Download a fresh consensus (and ENDIVE if using Walking
  634. Onions) from a random dirauth."""
  635. a = random.choice(self.dirauthaddrs)
  636. c = network.thenetwork.connect(self, a, self.perfstats)
  637. if network.thenetwork.womode == network.WOMode.VANILLA:
  638. if self.consensus is not None and \
  639. len(self.consensus.consdict['relays']) > 0:
  640. self.consensus = c.getconsensusdiff()
  641. else:
  642. self.consensus = c.getconsensus()
  643. self.relaypicker = dirauth.Consensus.verify(self.consensus,
  644. network.thenetwork.dirauthkeys(), self.perfstats)
  645. else:
  646. self.consensus = c.getconsensus()
  647. if self.endive is not None and \
  648. len(self.endive.enddict['snips']) > 0:
  649. self.endive = c.getendivediff()
  650. else:
  651. self.endive = c.getendive()
  652. self.relaypicker = dirauth.ENDIVE.verify(self.endive,
  653. self.consensus, network.thenetwork.dirauthkeys(),
  654. self.perfstats)
  655. c.close()
  656. def received_msg(self, msg, peeraddr, channel):
  657. """Callback when a NetMsg not specific to a circuit is
  658. received."""
  659. print("RelayChannelManager: Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  660. if isinstance(msg, RelayRandomHopMsg):
  661. if msg.ttl > 0:
  662. # Pick a random next hop from the consensus
  663. nexthop = self.relaypicker.pick_weighted_relay()
  664. if network.thenetwork.womode == network.WOMode.VANILLA:
  665. nextaddr = nexthop.descdict["addr"]
  666. else:
  667. nextaddr = nexthop.snipdict["addr"]
  668. self.send_msg(RelayRandomHopMsg(msg.ttl-1), nextaddr)
  669. elif isinstance(msg, RelayGetConsensusMsg):
  670. self.send_msg(RelayConsensusMsg(self.consensus), peeraddr)
  671. elif isinstance(msg, RelayGetConsensusDiffMsg):
  672. self.send_msg(RelayConsensusDiffMsg(self.consensus), peeraddr)
  673. elif isinstance(msg, VanillaCreateCircuitMsg):
  674. # A new circuit has arrived
  675. circhandler = channel.new_circuit_with_circid(msg.circid)
  676. # Create the ntor reply
  677. reply, secret = NTor.reply(self.onionkey, self.idpubkey,
  678. msg.ntor_request, self.perfstats)
  679. # Set up the circuit to use the shared secret
  680. enckey = nacl.hash.sha256(secret + b'downstream')
  681. deckey = nacl.hash.sha256(secret + b'upstream')
  682. circhandler.add_crypt_layer(enckey, deckey)
  683. # Add a handler for if an Extend Cell arrives (there should
  684. # be at most one on this circuit).
  685. circhandler.replace_celltype_handler(
  686. VanillaExtendCircuitCell, VanillaExtendCircuitHandler())
  687. # Send the ntor reply
  688. self.send_msg(CircuitCellMsg(msg.circid,
  689. VanillaCreatedCircuitCell(reply)), peeraddr)
  690. elif isinstance(msg, TelescopingCreateCircuitMsg):
  691. # A new circuit has arrived
  692. circhandler = channel.new_circuit_with_circid(msg.circid)
  693. # Create the ntor reply
  694. reply, secret = NTor.reply(self.onionkey, self.idpubkey,
  695. msg.ntor_request, self.perfstats)
  696. # Set up the circuit to use the shared secret
  697. enckey = nacl.hash.sha256(secret + b'downstream')
  698. deckey = nacl.hash.sha256(secret + b'upstream')
  699. circhandler.add_crypt_layer(enckey, deckey)
  700. # Add a handler for if an Extend Cell arrives (there should
  701. # be at most one on this circuit).
  702. circhandler.replace_celltype_handler(
  703. TelescopingExtendCircuitCell,
  704. TelescopingExtendCircuitHandler(self.relaypicker,
  705. self.idpubkey))
  706. # Send the ntor reply
  707. self.send_msg(CircuitCellMsg(msg.circid,
  708. TelescopingCreatedCircuitCell(reply)), peeraddr)
  709. else:
  710. return super().received_msg(msg, peeraddr, channel)
  711. def received_cell(self, circid, cell, peeraddr, channel):
  712. """Callback with a circuit-specific cell is received."""
  713. print("RelayChannelManager: Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  714. return super().received_cell(circid, cell, peeraddr, channel)
  715. class Relay(network.Server):
  716. """The class representing an onion relay."""
  717. def __init__(self, dirauthaddrs, bw, flags):
  718. # Gather performance statistics
  719. self.perfstats = network.PerfStats(network.EntType.RELAY)
  720. self.perfstats.is_bootstrapping = True
  721. # Create the identity and onion keys
  722. self.idkey = nacl.signing.SigningKey.generate()
  723. self.onionkey = nacl.public.PrivateKey.generate()
  724. self.perfstats.keygens += 2
  725. self.name = self.idkey.verify_key.encode(encoder=nacl.encoding.HexEncoder).decode("ascii")
  726. # Bind to the network to get a network address
  727. self.netaddr = network.thenetwork.bind(self)
  728. self.perfstats.name = "Relay at %s" % self.netaddr
  729. # Our bandwidth and flags
  730. self.bw = bw
  731. self.flags = flags
  732. # Register for epoch change notification
  733. network.thenetwork.wantepochticks(self, True, end=True)
  734. network.thenetwork.wantepochticks(self, True)
  735. # Create the RelayChannelManager connection manager
  736. self.channelmgr = RelayChannelManager(self.netaddr, dirauthaddrs,
  737. self.onionkey, self.idkey.verify_key, self.perfstats)
  738. # Initially, we're not a fallback relay
  739. self.is_fallbackrelay = False
  740. self.uploaddesc()
  741. def terminate(self):
  742. """Stop this relay."""
  743. if self.is_fallbackrelay:
  744. # Fallback relays must not (for now) terminate
  745. raise RelayFallbackTerminationError(self)
  746. # Stop listening for epoch ticks
  747. network.thenetwork.wantepochticks(self, False, end=True)
  748. network.thenetwork.wantepochticks(self, False)
  749. # Tell the dirauths we're going away
  750. self.uploaddesc(False)
  751. # Close connections to other relays
  752. self.channelmgr.terminate()
  753. # Stop listening to our own bound port
  754. self.close()
  755. def set_is_fallbackrelay(self, isfallback = True):
  756. """Set this relay to be a fallback relay (or unset if passed
  757. False)."""
  758. self.is_fallbackrelay = isfallback
  759. def epoch_ending(self, epoch):
  760. # Download the new consensus, which will have been created
  761. # already since the dirauths' epoch_ending callbacks happened
  762. # before the relays'.
  763. self.channelmgr.get_consensus()
  764. def newepoch(self, epoch):
  765. self.uploaddesc()
  766. def uploaddesc(self, upload=True):
  767. # Upload the descriptor for the epoch to come, or delete a
  768. # previous upload if upload=False
  769. descdict = dict();
  770. descdict["epoch"] = network.thenetwork.getepoch() + 1
  771. descdict["idkey"] = self.idkey.verify_key
  772. descdict["onionkey"] = self.onionkey.public_key
  773. descdict["addr"] = self.netaddr
  774. descdict["bw"] = self.bw
  775. descdict["flags"] = self.flags
  776. desc = dirauth.RelayDescriptor(descdict)
  777. desc.sign(self.idkey, self.perfstats)
  778. dirauth.RelayDescriptor.verify(desc, self.perfstats)
  779. if upload:
  780. descmsg = dirauth.DirAuthUploadDescMsg(desc)
  781. else:
  782. # Note that this relies on signatures being deterministic;
  783. # otherwise we'd need to save the descriptor we uploaded
  784. # before so we could tell the airauths to delete the exact
  785. # one
  786. descmsg = dirauth.DirAuthDelDescMsg(desc)
  787. # Upload them
  788. for a in self.channelmgr.dirauthaddrs:
  789. c = network.thenetwork.connect(self, a, self.perfstats)
  790. c.sendmsg(descmsg)
  791. c.close()
  792. def connected(self, peer):
  793. """Callback invoked when someone (client or relay) connects to
  794. us. Create a pair of linked Channels and return the peer half
  795. to the peer."""
  796. # Create the linked pair
  797. if peer is self.netaddr:
  798. # A self-loop? We'll allow it.
  799. peerchannel = Channel()
  800. peerchannel.peer = peerchannel
  801. peerchannel.next_circid = 2
  802. return peerchannel
  803. peerchannel = Channel()
  804. ourchannel = Channel()
  805. peerchannel.peer = ourchannel
  806. peerchannel.next_circid = 2
  807. ourchannel.peer = peerchannel
  808. ourchannel.next_circid = 1
  809. # Add our channel to the RelayChannelManager
  810. self.channelmgr.add_channel(ourchannel, peer)
  811. return peerchannel
  812. if __name__ == '__main__':
  813. perfstats = network.PerfStats(network.EntType.NONE)
  814. network.thenetwork.set_wo_style(network.WOMode.TELESCOPING,
  815. network.SNIPAuthMode.MERKLE)
  816. # Start some dirauths
  817. numdirauths = 9
  818. dirauthaddrs = []
  819. for i in range(numdirauths):
  820. dira = dirauth.DirAuth(i, numdirauths)
  821. dirauthaddrs.append(dira.netaddr)
  822. # Start some relays
  823. numrelays = 10
  824. relays = []
  825. for i in range(numrelays):
  826. # Relay bandwidths (at least the ones fast enough to get used)
  827. # in the live Tor network (as of Dec 2019) are well approximated
  828. # by (200000-(200000-25000)/3*log10(x)) where x is a
  829. # uniform integer in [1,2500]
  830. x = random.randint(1,2500)
  831. bw = int(200000-(200000-25000)/3*math.log10(x))
  832. relays.append(Relay(dirauthaddrs, bw, 0))
  833. # The fallback relays are a hardcoded list of about 5% of the
  834. # relays, used by clients for bootstrapping
  835. numfallbackrelays = int(numrelays * 0.05) + 1
  836. fallbackrelays = random.sample(relays, numfallbackrelays)
  837. for r in fallbackrelays:
  838. r.set_is_fallbackrelay()
  839. network.thenetwork.setfallbackrelays(fallbackrelays)
  840. # Tick the epoch
  841. network.thenetwork.nextepoch()
  842. print(dirauth.DirAuth.consensus)
  843. print(dirauth.DirAuth.endive)
  844. if network.thenetwork.womode == network.WOMode.VANILLA:
  845. relaypicker = dirauth.Consensus.verify(dirauth.DirAuth.consensus,
  846. network.thenetwork.dirauthkeys(), perfstats)
  847. else:
  848. relaypicker = dirauth.ENDIVE.verify(dirauth.DirAuth.endive,
  849. dirauth.DirAuth.consensus,
  850. network.thenetwork.dirauthkeys(), perfstats)
  851. if network.thenetwork.snipauthmode == \
  852. network.SNIPAuthMode.THRESHSIG:
  853. for s in dirauth.DirAuth.endive.enddict['snips']:
  854. dirauth.SNIP.verify(s, dirauth.DirAuth.consensus,
  855. network.thenetwork.dirauthkeys()[0], perfstats)
  856. print('ticked; epoch=', network.thenetwork.getepoch())
  857. relays[3].channelmgr.send_msg(RelayRandomHopMsg(30), relays[5].netaddr)
  858. # See what channels exist and do a consistency check
  859. for r in relays:
  860. print("%s: %s" % (r.netaddr, [ str(k) for k in r.channelmgr.channels.keys()]))
  861. raddr = r.netaddr
  862. for ad, ch in r.channelmgr.channels.items():
  863. if ch.peer.channelmgr.myaddr != ad:
  864. print('address mismatch:', raddr, ad, ch.peer.channelmgr.myaddr)
  865. if ch.peer.channelmgr.channels[raddr].peer is not ch:
  866. print('asymmetry:', raddr, ad, ch, ch.peer.channelmgr.channels[raddr].peer)
  867. # Stop some relays
  868. relays[3].terminate()
  869. del relays[3]
  870. relays[5].terminate()
  871. del relays[5]
  872. relays[7].terminate()
  873. del relays[7]
  874. # Tick the epoch
  875. network.thenetwork.nextepoch()
  876. print(dirauth.DirAuth.consensus)
  877. print(dirauth.DirAuth.endive)
  878. # See what channels exist and do a consistency check
  879. for r in relays:
  880. print("%s: %s" % (r.netaddr, [ str(k) for k in r.channelmgr.channels.keys()]))
  881. raddr = r.netaddr
  882. for ad, ch in r.channelmgr.channels.items():
  883. if ch.peer.channelmgr.myaddr != ad:
  884. print('address mismatch:', raddr, ad, ch.peer.channelmgr.myaddr)
  885. if ch.peer.channelmgr.channels[raddr].peer is not ch:
  886. print('asymmetry:', raddr, ad, ch, ch.peer.channelmgr.channels[raddr].peer)
  887. channel = relays[3].channelmgr.get_channel_to(relays[5].netaddr)
  888. circid, circhandler = channel.new_circuit()
  889. peerchannel = relays[5].channelmgr.get_channel_to(relays[3].netaddr)
  890. peerchannel.new_circuit_with_circid(circid)
  891. relays[3].channelmgr.send_cell(circid, StringCell("test"), relays[5].netaddr)
  892. idpubkey = relays[1].idkey.verify_key
  893. onionpubkey = relays[1].onionkey.public_key
  894. nt = NTor(perfstats)
  895. req = nt.request()
  896. R, S = NTor.reply(relays[1].onionkey, idpubkey, req, perfstats)
  897. S2 = nt.verify(R, onionpubkey, idpubkey)
  898. print(S == S2)
  899. # Test the Sphinx class: DH version (for the path selection keys)
  900. server1_key = nacl.public.PrivateKey.generate()
  901. server2_key = nacl.public.PrivateKey.generate()
  902. server3_key = nacl.public.PrivateKey.generate()
  903. client_key = nacl.public.PrivateKey.generate()
  904. # Check that normal DH is working
  905. ckey = nacl.public.Box(client_key, server1_key.public_key).shared_key()
  906. skey = nacl.public.Box(server1_key, client_key.public_key).shared_key()
  907. assert(ckey == skey)
  908. # Transform the client pubkey with Sphinx as it passes through the
  909. # servers and record the resulting shared secrets
  910. blinded_client_pubkey = client_key.public_key
  911. s1secret, blinded_client_pubkey = Sphinx.server(blinded_client_pubkey,
  912. server1_key, b'circuit', False, perfstats)
  913. s2secret, blinded_client_pubkey = Sphinx.server(blinded_client_pubkey,
  914. server2_key, b'circuit', False, perfstats)
  915. s3secret, _ = Sphinx.server(blinded_client_pubkey,
  916. server3_key, b'circuit', True, perfstats)
  917. # Hopefully matching keys on the client side
  918. blinding_keys = []
  919. c1secret, blind_key = Sphinx.client(client_key, blinding_keys,
  920. server1_key.public_key, b'circuit', False, perfstats)
  921. blinding_keys.append(blind_key)
  922. c2secret, blind_key = Sphinx.client(client_key, blinding_keys,
  923. server2_key.public_key, b'circuit', False, perfstats)
  924. blinding_keys.append(blind_key)
  925. c3secret, _ = Sphinx.client(client_key, blinding_keys,
  926. server3_key.public_key, b'circuit', True, perfstats)
  927. assert(s1secret == c1secret)
  928. assert(s2secret == c2secret)
  929. assert(s3secret == c3secret)
  930. print('Sphinx DH test successful')
  931. # End test of Sphinx (DH version)
  932. # Test the Sphinx class: NTor version (for the path selection keys)
  933. server1_idkey = nacl.signing.SigningKey.generate().verify_key
  934. server2_idkey = nacl.signing.SigningKey.generate().verify_key
  935. server3_idkey = nacl.signing.SigningKey.generate().verify_key
  936. server1_onionkey = nacl.public.PrivateKey.generate()
  937. server2_onionkey = nacl.public.PrivateKey.generate()
  938. server3_onionkey = nacl.public.PrivateKey.generate()
  939. client_ntor = NTor(perfstats)
  940. # Client's initial message
  941. client_pubkey = client_ntor.request()
  942. # Transform the client pubkey with Sphinx as it passes through the
  943. # servers and record the resulting shared secrets
  944. blinded_client_pubkey = client_pubkey
  945. (s1reply, s1secret), blinded_client_pubkey = NTor.reply(
  946. server1_onionkey, server1_idkey, blinded_client_pubkey,
  947. perfstats, b'data')
  948. (s2reply, s2secret), blinded_client_pubkey = NTor.reply(
  949. server2_onionkey, server2_idkey, blinded_client_pubkey,
  950. perfstats, b'data')
  951. (s3reply, s3secret) = NTor.reply(
  952. server3_onionkey, server3_idkey, blinded_client_pubkey,
  953. perfstats)
  954. # Hopefully matching keys on the client side
  955. c1secret = client_ntor.verify(s1reply, server1_onionkey.public_key,
  956. server1_idkey, b'data')
  957. c2secret = client_ntor.verify(s2reply, server2_onionkey.public_key,
  958. server2_idkey, b'data')
  959. c3secret = client_ntor.verify(s3reply, server3_onionkey.public_key,
  960. server3_idkey)
  961. assert(s1secret == c1secret)
  962. assert(s2secret == c2secret)
  963. assert(s3secret == c3secret)
  964. print('Sphinx NTor test successful')
  965. # End test of Sphinx (NTor version)