Browse Source

Start cleaning up Single-Pass Walking Onions

Ian Goldberg 4 years ago
parent
commit
afb53b921f
3 changed files with 64 additions and 34 deletions
  1. 16 7
      client.py
  2. 4 4
      dirauth.py
  3. 44 23
      relay.py

+ 16 - 7
client.py

@@ -284,7 +284,7 @@ class ClientChannelManager(relay.ChannelManager):
         return circhandler
 
     def new_circuit_telescoping(self):
-        """Create a new circuit from this client. (Telescoping Walking Onions
+        """Create a new circuit from this client (Telescoping Walking Onions
         version). If an error occurs and the circuit is deleted from the guard
         channel, return None, otherwise, return the circuit handler."""
 
@@ -331,7 +331,7 @@ class ClientChannelManager(relay.ChannelManager):
         return circhandler
 
     def new_circuit_singlepass(self):
-        """Create a new circuit from this client. (Single-Pass Walking Onions
+        """Create a new circuit from this client (Single-Pass Walking Onions
         version). If an error occurs and the circuit is deleted from the guard
         channel, return None, otherwise, return the circuit handler."""
 
@@ -342,19 +342,19 @@ class ClientChannelManager(relay.ChannelManager):
         circid, circhandler = guardchannel.new_circuit()
 
         # first, create the path-selection key used for Sphinx
-        client_key = nacl.public.PrivateKey.generate()
+        client_pathsel_key = nacl.public.PrivateKey.generate()
+        self.perfstats.keygens += 1
 
         # 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, ttl)
+                client_pathsel_key.public_key)
 
         # Set up the reply handler
         circhandler.replace_celltype_handler(
                 relay.SinglePassCreatedCircuitCell,
-                SinglePassCreatedHandler(self, ntor, client_key))
+                SinglePassCreatedHandler(self, ntor, client_pathsel_key))
 
         # Send the message
         guardchannel.send_msg(circcreatemsg)
@@ -366,8 +366,17 @@ class ClientChannelManager(relay.ChannelManager):
             logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
             return None
 
-        return circhandler
+        # In Single-Pass Walking Onions, we need to check whether the
+        # circuit got into a loop (guard equals exit); each node will
+        # refuse to extend to itself, so this is the only possible loop
+        # in a circuit of length 3
+        if circhandler.circuit_descs[0].snipdict["addr"] == \
+                circhandler.circuit_descs[2].snipdict["addr"]:
+            logging.debug("circuit in a loop")
+            circhandler.close()
+            circhandler = None
 
+        return circhandler
 
     def new_circuit(self):
         """Create a new circuit from this client."""

+ 4 - 4
dirauth.py

@@ -60,7 +60,7 @@ class RelayDescriptor:
 #  onionkey: a public onion key
 #  addr: a network address
 #  flags: relay flags
-#  vrfkey: a VRF public key (Single-Pass Walking Onions only)
+#  pathselkey: a path selection public key (Single-Pass Walking Onions only)
 #  range: the (lo,hi) values for the index range (lo is inclusive, hi is
 #         exclusive; that is, x is in the range if lo <= x < hi).
 #         lo=hi denotes an empty range.
@@ -77,10 +77,10 @@ class SNIP:
 
     def __str__(self, withauth = True):
         res = "SNIP [\n"
-        for k in ["epoch", "idkey", "onionkey", "addr", "flags",
-                    "vrfkey", "range", "auth"]:
+        for k in ["epoch", "idkey", "onionkey", "pathselkey", "addr",
+                    "flags", "range", "auth"]:
             if k in self.snipdict:
-                if k == "idkey" or k == "onionkey":
+                if k == "idkey" or k == "onionkey" or k == "pathselkey":
                     res += "  " + k + ": " + nacl.encoding.HexEncoder.encode(self.snipdict[k]).decode("ascii") + "\n"
                 elif k == "auth":
                     if withauth:

+ 44 - 23
relay.py

@@ -156,6 +156,7 @@ class VanillaExtendedCircuitCell(RelayCell):
     def __init__(self, ntor_reply):
         self.ntor_reply = ntor_reply
 
+
 # It is intentional that TelescopingCreateCircuitMsg is a RelayNetMsg and
 # not a RelayCell.  This is the message that _creates_ the circuit, so
 # it can't be sent as a cell _within_ the circuit.
@@ -167,6 +168,7 @@ class TelescopingCreateCircuitMsg(RelayNetMsg):
         self.circid = circid
         self.ntor_request = ntor_request
 
+
 class TelescopingCreatedCircuitCell(RelayCell):
     """The message for responding to circuit creation in Telescoping Walking
     Onions."""
@@ -174,6 +176,7 @@ class TelescopingCreatedCircuitCell(RelayCell):
     def __init__(self, ntor_reply):
         self.ntor_reply = ntor_reply
 
+
 class TelescopingExtendCircuitCell(RelayCell):
     """The message for requesting circuit extension in Telescoping Walking
     Onions."""
@@ -182,6 +185,7 @@ class TelescopingExtendCircuitCell(RelayCell):
         self.idx = idx
         self.ntor_request = ntor_request
 
+
 class TelescopingExtendedCircuitCell(RelayCell):
     """The message for responding to circuit extension in Telescoping Walking
     Onions."""
@@ -190,28 +194,28 @@ class TelescopingExtendedCircuitCell(RelayCell):
         self.ntor_reply = ntor_reply
         self.snip = snip
 
+
 class SinglePassCreateCircuitMsg(RelayNetMsg):
     """The message for requesting circuit creation in Single Pass Onion
     Routing. This is used to extend a Single-Pass circuit by clients."""
 
-    def __init__(self, circid, ntor_request, client_path_selection_key, ttl):
+    def __init__(self, circid, ntor_request, client_path_selection_key,
+            ttl=2):
         self.circid = circid
         self.ntor_request = ntor_request
-        self.client_path_selection_key = client_path_selection_key
+        self.clipathselkey = bytes(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, snip, vrf_output, ttl, encrypted_cell):
+    def __init__(self, ntor_reply, encrypted_cell):
         self.ntor_reply = ntor_reply
-        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
+        # The above field is sent in plaintext; the below is an
+        # SinglePassCreatedEnc, or None if this is the last layer
+        self.enc = encrypted_cell
 
 
 class EncryptedCell(RelayCell):
@@ -235,6 +239,14 @@ class EncryptedCell(RelayCell):
         return self.plaintext.size()
 
 
+class SinglePassCreatedEnc(EncryptedCell):
+    """The nested encrypted informaion inside a
+    SinglePassCreatedCircuitCell.  nextlayer is itself a
+    SinglePassCreatedCircuitCell."""
+    def __init__(self, key, snip, vrf_output, nextlayer):
+        super().__init__(key, (snip, vrf_output, nextlayer))
+
+
 class RelayFallbackTerminationError(Exception):
     """An exception raised when someone tries to terminate a fallback
     relay."""
@@ -545,24 +557,24 @@ 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):
+    def __init__(self, ntorreply, next_snip, vrf_output, enckey):
         self.ntorreply = ntorreply
         self.next_snip = next_snip
         self.vrf_output = vrf_output
-        self.ttl = ttl
+        self.enckey = enckey
 
     def received_cell(self, circhandler, cell):
-        logging.debug("Handle a SinglePassCreatedCircuitCell received by a relay, my ttl is %s", str(self.ttl))
+        logging.debug("Handle a SinglePassCreatedCircuitCell received by a relay")
         # 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))
+        logging.debug("Sending a SinglePassCreatedCell after receiving one from %s", self.next_snip.snipdict['addr'])
 
-        # Just forward a SinglePassCreatedCircuitCell back towards the
-        # client
+        # 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))
+            SinglePassCreatedCircuitCell(cell.ntor_reply,
+                SinglePassCreatedEnc(self.enckey, self.next_snip,
+                self.vrf_output, cell)))
 
 
 class CircuitHandler:
@@ -957,7 +969,7 @@ class RelayChannelManager(ChannelManager):
             # 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)
+                SinglePassCreatedCircuitCell(reply, None)), peeraddr)
 
         elif isinstance(msg, SinglePassCreateCircuitMsg) and msg.ttl > 0:
             logging.debug("RelayChannelManager: Single-Pass TTL is greater than 0; extending")
@@ -974,13 +986,13 @@ class RelayChannelManager(ChannelManager):
             # circuit extension key
             enckey = nacl.hash.sha256(secret + b'downstream')
             deckey = nacl.hash.sha256(secret + b'upstream')
-            circhandler.add_crypt_layer(enckey, deckey)
 
             # 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)
+            idx_as_hex, blinded_client_path_selection_key = \
+                    Sphinx.server(nacl.public.PublicKey(msg.clipathselkey),
+                    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.")
@@ -1011,12 +1023,15 @@ class RelayChannelManager(ChannelManager):
             newcirchandler.replace_celltype_handler(
                     SinglePassCreatedCircuitCell,
                     SinglePassCreatedRelayHandler(ntorreply, next_hop,
-                        vrf_output, msg.ttl))
+                        vrf_output, enckey))
 
             # 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))
+
+            # Now set up the crypto
+            circhandler.add_crypt_layer(enckey, deckey)
         else:
             return super().received_msg(msg, peeraddr, channel)
 
@@ -1052,8 +1067,10 @@ class Relay(network.Server):
         network.thenetwork.wantepochticks(self, True, end=True)
         network.thenetwork.wantepochticks(self, True)
 
+        # Create the path selection key for Single-Pass Walking Onions
         if network.thenetwork.womode == network.WOMode.SINGLEPASS:
             self.path_selection_key = nacl.public.PrivateKey.generate()
+            self.perfstats.keygens += 1
         else:
             self.path_selection_key = None
 
@@ -1098,6 +1115,10 @@ class Relay(network.Server):
         self.channelmgr.get_consensus()
 
     def newepoch(self, epoch):
+        # Rotate the path selection key for Single-Pass Walking Onions
+        if network.thenetwork.womode == network.WOMode.SINGLEPASS:
+            self.path_selection_key = nacl.public.PrivateKey.generate()
+            self.perfstats.keygens += 1
         self.uploaddesc()
 
     def uploaddesc(self, upload=True):
@@ -1112,7 +1133,7 @@ class Relay(network.Server):
         descdict["flags"] = self.flags
 
         if network.thenetwork.womode == network.WOMode.SINGLEPASS:
-            descdict["path_selection_key"] = self.path_selection_key
+            descdict["pathselkey"] = bytes(self.path_selection_key)
 
         desc = dirauth.RelayDescriptor(descdict)
         desc.sign(self.idkey, self.perfstats)