|
@@ -6,6 +6,7 @@ import math
|
|
|
import nacl.utils
|
|
|
import nacl.signing
|
|
|
import nacl.public
|
|
|
+import nacl.hash
|
|
|
|
|
|
import network
|
|
|
import dirauth
|
|
@@ -70,6 +71,61 @@ class RelayFallbackTerminationError(Exception):
|
|
|
relay."""
|
|
|
|
|
|
|
|
|
+class NTor:
|
|
|
+ """A class implementing the ntor one-way authenticated key agreement
|
|
|
+ scheme. The details are not exactly the same as either the ntor
|
|
|
+ paper or Tor's implementation, but it will agree on keys and have
|
|
|
+ the same number of public key operations."""
|
|
|
+
|
|
|
+ def __init__(self, perfstats):
|
|
|
+ self.perfstats = perfstats
|
|
|
+
|
|
|
+ def request(self):
|
|
|
+ """Create the ntor request message: X = g^x."""
|
|
|
+ self.client_ephem_key = nacl.public.PrivateKey.generate()
|
|
|
+ self.perfstats.keygens += 1
|
|
|
+ return self.client_ephem_key.public_key
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def reply(onion_privkey, idpubkey, client_pubkey, perfstats):
|
|
|
+ """The server calls this static method to produce the ntor reply
|
|
|
+ message: (Y = g^y, B = g^b, A = H(M, "verify")) and the shared
|
|
|
+ secret S = H(M, "secret") for M = (X^y,X^b,ID,B,X,Y)."""
|
|
|
+ server_ephem_key = nacl.public.PrivateKey.generate()
|
|
|
+ perfstats.keygens += 1
|
|
|
+ xykey = nacl.public.Box(server_ephem_key, client_pubkey).shared_key()
|
|
|
+ xbkey = nacl.public.Box(onion_privkey, client_pubkey).shared_key()
|
|
|
+ perfstats.dhs += 2
|
|
|
+ M = xykey + xbkey + \
|
|
|
+ idpubkey.encode(encoder=nacl.encoding.RawEncoder) + \
|
|
|
+ onion_privkey.public_key.encode(encoder=nacl.encoding.RawEncoder) + \
|
|
|
+ server_ephem_key.public_key.encode(encoder=nacl.encoding.RawEncoder)
|
|
|
+ A = nacl.hash.sha256(M + b'verify', encoder=nacl.encoding.RawEncoder)
|
|
|
+ S = nacl.hash.sha256(M + b'secret', encoder=nacl.encoding.RawEncoder)
|
|
|
+ return ((server_ephem_key.public_key, onion_privkey.public_key, A), \
|
|
|
+ S)
|
|
|
+
|
|
|
+ def verify(self, reply, onion_pubkey, idpubkey):
|
|
|
+ """The client calls this method to verify the ntor reply
|
|
|
+ message, passing the onion and id public keys for the server
|
|
|
+ it's expecting to be talking to . Returns the shared secret on
|
|
|
+ success, or raises ValueError on failure."""
|
|
|
+ server_ephem_pubkey, server_onion_pubkey, authtag = reply
|
|
|
+ if onion_pubkey != server_onion_pubkey:
|
|
|
+ raise ValueError("NTor onion pubkey mismatch")
|
|
|
+ xykey = nacl.public.Box(self.client_ephem_key, server_ephem_pubkey).shared_key()
|
|
|
+ xbkey = nacl.public.Box(self.client_ephem_key, onion_pubkey).shared_key()
|
|
|
+ self.perfstats.dhs += 2
|
|
|
+ M = xykey + xbkey + \
|
|
|
+ idpubkey.encode(encoder=nacl.encoding.RawEncoder) + \
|
|
|
+ onion_pubkey.encode(encoder=nacl.encoding.RawEncoder) + \
|
|
|
+ server_ephem_pubkey.encode(encoder=nacl.encoding.RawEncoder)
|
|
|
+ Acheck = nacl.hash.sha256(M + b'verify', encoder=nacl.encoding.RawEncoder)
|
|
|
+ S = nacl.hash.sha256(M + b'secret', encoder=nacl.encoding.RawEncoder)
|
|
|
+ if Acheck != authtag:
|
|
|
+ raise ValueError("NTor auth mismatch")
|
|
|
+ return S
|
|
|
+
|
|
|
class CircuitHandler:
|
|
|
"""A class for managing sending and receiving encrypted cells on a
|
|
|
particular circuit."""
|
|
@@ -463,3 +519,11 @@ if __name__ == '__main__':
|
|
|
peerchannel = relays[5].cellhandler.get_channel_to(relays[3].netaddr)
|
|
|
peerchannel.new_circuit_with_circid(circid)
|
|
|
relays[3].cellhandler.send_cell(circid, network.StringNetMsg("test"), relays[5].netaddr)
|
|
|
+
|
|
|
+ idpubkey = dirauth.DirAuth.consensus.consdict["relays"][1].descdict["idkey"]
|
|
|
+ onionpubkey = dirauth.DirAuth.consensus.consdict["relays"][1].descdict["onionkey"]
|
|
|
+ nt = NTor(perfstats)
|
|
|
+ req = nt.request()
|
|
|
+ R, S = NTor.reply(relays[1].onionkey, idpubkey, req, perfstats)
|
|
|
+ S2 = nt.verify(R, onionpubkey, idpubkey)
|
|
|
+ print(S == S2)
|