Browse Source

Merge branch 'master' of git-crysp.uwaterloo.ca:walkingonions

Ian Goldberg 4 years ago
parent
commit
b5d0eedd1f
2 changed files with 133 additions and 44 deletions
  1. 20 4
      client.py
  2. 113 40
      relay.py

+ 20 - 4
client.py

@@ -163,6 +163,19 @@ class TelescopingExtendedHandler:
         # Send the cell
         circhandler.send_cell(circextendmsg)
 
+class SinglePassCreatedHandler:
+    """A handler for SinglePassCreatedCircuitCell cells."""
+
+    def __init__(self, channelmgr, ntor, client_key):
+        self.channelmgr = channelmgr
+        self.ntor = ntor
+        self.client_key = client_key
+
+    def received_cell(self, circhandler, cell):
+        logging.debug("Received cell in SinglePassCreatedHandler")
+        sys.exit("not yet implemented")
+
+
 class ClientChannelManager(relay.ChannelManager):
     """The subclass of ChannelManager for clients."""
 
@@ -315,8 +328,14 @@ class ClientChannelManager(relay.ChannelManager):
         # Construct the SinglePassCreateCircuitMsg
         ntor = relay.NTor(self.perfstats)
         ntor_request = ntor.request()
+        ttl = 2 # TODO set a default for the msg type
         circcreatemsg = relay.SinglePassCreateCircuitMsg(circid, ntor_request,
-                client_key.public_key)
+                client_key.public_key, ttl)
+
+        # Set up the reply handler
+        circhandler.replace_celltype_handler(
+                relay.SinglePassCreatedCircuitCell,
+                SinglePassCreatedHandler(self, ntor, client_key))
 
         # Send the message
         guardchannel.send_msg(circcreatemsg)
@@ -328,9 +347,6 @@ class ClientChannelManager(relay.ChannelManager):
             logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
             return None
 
-        #TODO validate the Sphinx keys on reply
-        logging.warning("Unimplemented! The client should validate the NTOR handshake using Sphinx operations here.")
-
         return circhandler
 
 

+ 113 - 40
relay.py

@@ -10,10 +10,26 @@ import nacl.signing
 import nacl.public
 import nacl.hash
 import nacl.bindings
+import hashlib
+
+from collections import namedtuple
 
 import network
 import dirauth
 
+VRFOutput = namedtuple('VRFOutput', 'output proof')
+
+class VRF():
+    def __init__(self, private_key, relaypicker):
+        self.private_key = private_key
+        self.relaypicker = relaypicker
+
+    def get_output(self, vrf_input):
+        output = self.relaypicker.pick_weighted_relay_index()
+        m = hashlib.sha256()
+        m.update(str(output).encode())
+        return VRFOutput(output, m.digest())
+
 class RelayNetMsg(network.NetMsg):
     """The subclass of NetMsg for messages between relays and either
     relays or clients."""
@@ -160,7 +176,7 @@ class TelescopingCreatedCircuitCell(RelayCell):
 
 class TelescopingExtendCircuitCell(RelayCell):
     """The message for requesting circuit extension in Telescoping Walking
-    Onionss."""
+    Onions."""
 
     def __init__(self, idx, ntor_request):
         self.idx = idx
@@ -176,20 +192,26 @@ class TelescopingExtendedCircuitCell(RelayCell):
 
 class SinglePassCreateCircuitMsg(RelayNetMsg):
     """The message for requesting circuit creation in Single Pass Onion
-    Routing."""
+    Routing. This is used to extend a Single-Pass circuit by clients."""
 
-    def __init__(self, circid, ntor_request, client_path_selection_key):
+    def __init__(self, circid, ntor_request, client_path_selection_key, ttl):
         self.circid = circid
         self.ntor_request = ntor_request
         self.client_path_selection_key = client_path_selection_key
+        self.ttl = ttl
 
 class SinglePassCreatedCircuitCell(RelayCell):
     """The message for responding to circuit creation in Single-Pass Walking
     Onions."""
 
-    def __init__(self, ntor_reply, circuit_snips):
+    def __init__(self, ntor_reply, snip, vrf_output, ttl, encrypted_cell):
         self.ntor_reply = ntor_reply
-        self.circuit_snips = circuit_snips
+        self.snip = snip
+        self.vrf_output = vrf_output
+        self.ttl = ttl
+        # the above fields are sent in plaintext, the below is encrypted
+        # information
+        self.encrypted_cell = encrypted_cell
 
 
 class EncryptedCell(RelayCell):
@@ -498,7 +520,7 @@ class TelescopingCreatedRelayHandler:
     def received_cell(self, circhandler, cell):
         logging.debug("Handle a TelescopingCreatedCircuit received by a relay")
         # Remove ourselves from handling a second
-        # VanillaCreatedCircuitCell on this circuit
+        # TelescopingCreatedCircuitCell on this circuit
         circhandler.replace_celltype_handler(TelescopingCreatedCircuitCell, None)
 
         # Just forward a TelescopingExtendedCircuitCell back towards the
@@ -506,6 +528,30 @@ class TelescopingCreatedRelayHandler:
         circhandler.adjacent_circuit_handler.send_cell(
             TelescopingExtendedCircuitCell(cell.ntor_reply, self.next_snip))
 
+class SinglePassCreatedRelayHandler:
+    """Handle a SinglePassCreatedCircuitCell received by a _relay_ that
+    recently received a SinglePassCreateCircuitMsg from this relay."""
+
+    def __init__(self, ntorreply, next_snip, vrf_output, ttl):
+        self.ntorreply = ntorreply
+        self.next_snip = next_snip
+        self.vrf_output = vrf_output
+        self.ttl = ttl
+
+    def received_cell(self, circhandler, cell):
+        logging.debug("Handle a SinglePassCreatedCircuitCell received by a relay, my ttl is %s", str(self.ttl))
+        # Remove ourselves from handling a second
+        # SinglePassCreatedCircuitCell on this circuit
+        circhandler.replace_celltype_handler(SinglePassCreatedCircuitCell, None)
+        logging.debug("Sending a SinglePassCreatedCell after receiving one from %s; my ttl is %s", self.next_snip.snipdict['addr'], str(self.ttl))
+
+        # Just forward a SinglePassCreatedCircuitCell back towards the
+        # client
+        circhandler.adjacent_circuit_handler.channel_send_cell(
+            SinglePassCreatedCircuitCell(cell.ntor_reply, self.next_snip,
+                self.vrf_output, self.ttl, cell))
+
+
 class CircuitHandler:
     """A class for managing sending and receiving encrypted cells on a
     particular circuit."""
@@ -879,11 +925,36 @@ class RelayChannelManager(ChannelManager):
             # Send the ntor reply
             self.send_msg(CircuitCellMsg(msg.circid,
                     TelescopingCreatedCircuitCell(reply)), peeraddr)
-        elif isinstance(msg, SinglePassCreateCircuitMsg):
+        elif isinstance(msg, SinglePassCreateCircuitMsg) and msg.ttl == 0:
+            # we are the end of the circuit, just establish a shared key and
+            # return
+            logging.debug("RelayChannelManager: Single-Pass TTL is 0, replying without extending")
+
             # A new circuit has arrived
             circhandler = channel.new_circuit_with_circid(msg.circid)
-            # Create the ntor reply for the circuit-extension key
-            (reply, secret), blinded_client_pubkey = NTor.reply(self.onionkey, self.idpubkey,
+
+            # Create the ntor reply
+            reply, secret = NTor.reply(self.onionkey, self.idpubkey,
+                msg.ntor_request, self.perfstats)
+            # Set up the circuit to use the shared secret
+            enckey = nacl.hash.sha256(secret + b'downstream')
+            deckey = nacl.hash.sha256(secret + b'upstream')
+            circhandler.add_crypt_layer(enckey, deckey)
+
+            # Send the ntor reply, but no need to send the snip for the next
+            # relay or vrf proof, as this is the last relay in the circuit.
+            self.send_msg(CircuitCellMsg(msg.circid,
+                SinglePassCreatedCircuitCell(reply, None, None, 0, None)), peeraddr)
+
+        elif isinstance(msg, SinglePassCreateCircuitMsg) and msg.ttl > 0:
+            logging.debug("RelayChannelManager: Single-Pass TTL is greater than 0; extending")
+
+            # A new circuit has arrived
+            circhandler = channel.new_circuit_with_circid(msg.circid)
+
+            # Create the ntor reply for the circuit-extension key, and derive
+            # the client's next blinded key
+            (ntorreply, secret), blinded_client_encr_key = NTor.reply(self.onionkey, self.idpubkey,
                     msg.ntor_request, self.perfstats,  b'circuit')
 
             # Set up the circuit to use the shared secret established from the
@@ -892,45 +963,47 @@ class RelayChannelManager(ChannelManager):
             deckey = nacl.hash.sha256(secret + b'upstream')
             circhandler.add_crypt_layer(enckey, deckey)
 
-            logging.debug("WARNING: Unimplemented! Should check the TTL, if it isn't \
-                    zero, decriment it and pass it along. Otherwise, just reply \
-                    without extending.")
-
-            # here, we will directly extend the circuit ourselves, after doing
-            # the following:
-            # 1. determining the next relay using the client's path selection
-            #    key in conjunction with our own
-            # 2. blinding each of the client's public keys to send to the next
-            #    hop.
-            idx_as_hex, blinded_client_pubkey = Sphinx.server(msg.client_path_selection_key,
+            # here, we will directly extend the circuit ourselves, after
+            # determining the next relay using the client's path selection
+            # key in conjunction with our own
+            idx_as_hex, blinded_client_path_selection_key = Sphinx.server(msg.client_path_selection_key,
             self.path_selection_key, b'circuit', False, self.perfstats)
 
             logging.debug("RelayChannelManager: Unimplemented! need to translate idx into endive index")
+            logging.debug("RelayChannelManager: Unimplemented! need to pick the next relay using the shared secret between the client and the relay.")
+
+            # simpulate the VRF output for now
+            vrf_output = VRF(self.path_selection_key,
+                    self.relaypicker).get_output(idx_as_hex)
 
-            sys.exit("TODO convert shared secret into integer type modulo alpha")
-            nexthop = self.relaypicker.pick_relay_by_uniform_index(idx)
-            if nexthop == None:
-                logging.debug("WARNING: Unimplemented! Need to validate next hop is not null, if it is, we should send a CLOSE cell.")
+            next_hop = self.relaypicker.pick_relay_by_uniform_index(vrf_output.output)
+            if next_hop == None:
+                logging.debug("Client requested extending the circuit to a relay index that results in None, aborting. my circid: %s", str(circhandler.circid))
+                circhandler.close()
+            elif next_hop.snipdict["idkey"] == self.idpubkey or next_hop.snipdict["addr"] == peeraddr:
+                logging.debug("Client requested extending the circuit to a relay already in the path; aborting. my circid: %s", str(circhandler.circid))
+                circhandler.close()
+
+            # Allocate a new circuit id to the requested next hop
+            channelmgr = circhandler.channel.channelmgr
+            nexthopchannel = channelmgr.get_channel_to(next_hop.snipdict["addr"])
+            newcircid, newcirchandler = nexthopchannel.new_circuit()
+
+            # Connect the existing and new circuits together
+            circhandler.adjacent_circuit_handler = newcirchandler
+            newcirchandler.adjacent_circuit_handler = circhandler
 
             # Add a handler for once the next relay replies to say that the
             # circuit has been created
-            # be at most one on this circuit).
-            circhandler.replace_celltype_handler(
+            newcirchandler.replace_celltype_handler(
                     SinglePassCreatedCircuitCell,
-                    SinglePassCreatedCircuitHandler(ntorreply, next_hop))
-
-#            # Allocate a new circuit id to the requested next hop
-#            channelmgr = circhandler.channel.channelmgr
-#            nexthopchannel = channelmgr.get_channel_to(next_snip.snipdict["addr"])
-#            newcircid, newcirchandler = nexthopchannel.new_circuit()
-#
-#            # Send the next create message to the next hop
-#            # TODO add the correct interface here
-#            self.send_msg(CircuitCellMsg(msg.circid,
-#                    SinglePassCreateCircuitCell(newcircid, ntorrequest,
-#                        next_client_path_selection_key)), peeraddr)
-#
-            sys.exit("have not yet implemented circuit handling for single-pass in relays")
+                    SinglePassCreatedRelayHandler(ntorreply, next_hop,
+                        vrf_output, msg.ttl))
+
+            # Send the next create message to the next hop
+            nexthopchannel.send_msg(SinglePassCreateCircuitMsg(newcircid,
+                blinded_client_encr_key, blinded_client_path_selection_key,
+                msg.ttl-1))
         else:
             return super().received_msg(msg, peeraddr, channel)