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
         return circhandler
 
 
     def new_circuit_telescoping(self):
     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
         version). If an error occurs and the circuit is deleted from the guard
         channel, return None, otherwise, return the circuit handler."""
         channel, return None, otherwise, return the circuit handler."""
 
 
@@ -331,7 +331,7 @@ class ClientChannelManager(relay.ChannelManager):
         return circhandler
         return circhandler
 
 
     def new_circuit_singlepass(self):
     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
         version). If an error occurs and the circuit is deleted from the guard
         channel, return None, otherwise, return the circuit handler."""
         channel, return None, otherwise, return the circuit handler."""
 
 
@@ -342,19 +342,19 @@ class ClientChannelManager(relay.ChannelManager):
         circid, circhandler = guardchannel.new_circuit()
         circid, circhandler = guardchannel.new_circuit()
 
 
         # first, create the path-selection key used for Sphinx
         # 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
         # Construct the SinglePassCreateCircuitMsg
         ntor = relay.NTor(self.perfstats)
         ntor = relay.NTor(self.perfstats)
         ntor_request = ntor.request()
         ntor_request = ntor.request()
-        ttl = 2 # TODO set a default for the msg type
         circcreatemsg = relay.SinglePassCreateCircuitMsg(circid, ntor_request,
         circcreatemsg = relay.SinglePassCreateCircuitMsg(circid, ntor_request,
-                client_key.public_key, ttl)
+                client_pathsel_key.public_key)
 
 
         # Set up the reply handler
         # Set up the reply handler
         circhandler.replace_celltype_handler(
         circhandler.replace_celltype_handler(
                 relay.SinglePassCreatedCircuitCell,
                 relay.SinglePassCreatedCircuitCell,
-                SinglePassCreatedHandler(self, ntor, client_key))
+                SinglePassCreatedHandler(self, ntor, client_pathsel_key))
 
 
         # Send the message
         # Send the message
         guardchannel.send_msg(circcreatemsg)
         guardchannel.send_msg(circcreatemsg)
@@ -366,8 +366,17 @@ class ClientChannelManager(relay.ChannelManager):
             logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
             logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
             return None
             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):
     def new_circuit(self):
         """Create a new circuit from this client."""
         """Create a new circuit from this client."""

+ 4 - 4
dirauth.py

@@ -60,7 +60,7 @@ class RelayDescriptor:
 #  onionkey: a public onion key
 #  onionkey: a public onion key
 #  addr: a network address
 #  addr: a network address
 #  flags: relay flags
 #  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
 #  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).
 #         exclusive; that is, x is in the range if lo <= x < hi).
 #         lo=hi denotes an empty range.
 #         lo=hi denotes an empty range.
@@ -77,10 +77,10 @@ class SNIP:
 
 
     def __str__(self, withauth = True):
     def __str__(self, withauth = True):
         res = "SNIP [\n"
         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 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"
                     res += "  " + k + ": " + nacl.encoding.HexEncoder.encode(self.snipdict[k]).decode("ascii") + "\n"
                 elif k == "auth":
                 elif k == "auth":
                     if withauth:
                     if withauth:

+ 44 - 23
relay.py

@@ -156,6 +156,7 @@ class VanillaExtendedCircuitCell(RelayCell):
     def __init__(self, ntor_reply):
     def __init__(self, ntor_reply):
         self.ntor_reply = ntor_reply
         self.ntor_reply = ntor_reply
 
 
+
 # It is intentional that TelescopingCreateCircuitMsg is a RelayNetMsg and
 # It is intentional that TelescopingCreateCircuitMsg is a RelayNetMsg and
 # not a RelayCell.  This is the message that _creates_ the circuit, so
 # not a RelayCell.  This is the message that _creates_ the circuit, so
 # it can't be sent as a cell _within_ the circuit.
 # it can't be sent as a cell _within_ the circuit.
@@ -167,6 +168,7 @@ class TelescopingCreateCircuitMsg(RelayNetMsg):
         self.circid = circid
         self.circid = circid
         self.ntor_request = ntor_request
         self.ntor_request = ntor_request
 
 
+
 class TelescopingCreatedCircuitCell(RelayCell):
 class TelescopingCreatedCircuitCell(RelayCell):
     """The message for responding to circuit creation in Telescoping Walking
     """The message for responding to circuit creation in Telescoping Walking
     Onions."""
     Onions."""
@@ -174,6 +176,7 @@ class TelescopingCreatedCircuitCell(RelayCell):
     def __init__(self, ntor_reply):
     def __init__(self, ntor_reply):
         self.ntor_reply = ntor_reply
         self.ntor_reply = ntor_reply
 
 
+
 class TelescopingExtendCircuitCell(RelayCell):
 class TelescopingExtendCircuitCell(RelayCell):
     """The message for requesting circuit extension in Telescoping Walking
     """The message for requesting circuit extension in Telescoping Walking
     Onions."""
     Onions."""
@@ -182,6 +185,7 @@ class TelescopingExtendCircuitCell(RelayCell):
         self.idx = idx
         self.idx = idx
         self.ntor_request = ntor_request
         self.ntor_request = ntor_request
 
 
+
 class TelescopingExtendedCircuitCell(RelayCell):
 class TelescopingExtendedCircuitCell(RelayCell):
     """The message for responding to circuit extension in Telescoping Walking
     """The message for responding to circuit extension in Telescoping Walking
     Onions."""
     Onions."""
@@ -190,28 +194,28 @@ class TelescopingExtendedCircuitCell(RelayCell):
         self.ntor_reply = ntor_reply
         self.ntor_reply = ntor_reply
         self.snip = snip
         self.snip = snip
 
 
+
 class SinglePassCreateCircuitMsg(RelayNetMsg):
 class SinglePassCreateCircuitMsg(RelayNetMsg):
     """The message for requesting circuit creation in Single Pass Onion
     """The message for requesting circuit creation in Single Pass Onion
     Routing. This is used to extend a Single-Pass circuit by clients."""
     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.circid = circid
         self.ntor_request = ntor_request
         self.ntor_request = ntor_request
-        self.client_path_selection_key = client_path_selection_key
+        self.clipathselkey = bytes(client_path_selection_key)
         self.ttl = ttl
         self.ttl = ttl
 
 
+
 class SinglePassCreatedCircuitCell(RelayCell):
 class SinglePassCreatedCircuitCell(RelayCell):
     """The message for responding to circuit creation in Single-Pass Walking
     """The message for responding to circuit creation in Single-Pass Walking
     Onions."""
     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.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):
 class EncryptedCell(RelayCell):
@@ -235,6 +239,14 @@ class EncryptedCell(RelayCell):
         return self.plaintext.size()
         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):
 class RelayFallbackTerminationError(Exception):
     """An exception raised when someone tries to terminate a fallback
     """An exception raised when someone tries to terminate a fallback
     relay."""
     relay."""
@@ -545,24 +557,24 @@ class SinglePassCreatedRelayHandler:
     """Handle a SinglePassCreatedCircuitCell received by a _relay_ that
     """Handle a SinglePassCreatedCircuitCell received by a _relay_ that
     recently received a SinglePassCreateCircuitMsg from this relay."""
     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.ntorreply = ntorreply
         self.next_snip = next_snip
         self.next_snip = next_snip
         self.vrf_output = vrf_output
         self.vrf_output = vrf_output
-        self.ttl = ttl
+        self.enckey = enckey
 
 
     def received_cell(self, circhandler, cell):
     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
         # Remove ourselves from handling a second
         # SinglePassCreatedCircuitCell on this circuit
         # SinglePassCreatedCircuitCell on this circuit
         circhandler.replace_celltype_handler(SinglePassCreatedCircuitCell, None)
         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(
         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:
 class CircuitHandler:
@@ -957,7 +969,7 @@ class RelayChannelManager(ChannelManager):
             # Send the ntor reply, but no need to send the snip for the next
             # 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.
             # relay or vrf proof, as this is the last relay in the circuit.
             self.send_msg(CircuitCellMsg(msg.circid,
             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:
         elif isinstance(msg, SinglePassCreateCircuitMsg) and msg.ttl > 0:
             logging.debug("RelayChannelManager: Single-Pass TTL is greater than 0; extending")
             logging.debug("RelayChannelManager: Single-Pass TTL is greater than 0; extending")
@@ -974,13 +986,13 @@ class RelayChannelManager(ChannelManager):
             # circuit extension key
             # circuit extension key
             enckey = nacl.hash.sha256(secret + b'downstream')
             enckey = nacl.hash.sha256(secret + b'downstream')
             deckey = nacl.hash.sha256(secret + b'upstream')
             deckey = nacl.hash.sha256(secret + b'upstream')
-            circhandler.add_crypt_layer(enckey, deckey)
 
 
             # here, we will directly extend the circuit ourselves, after
             # here, we will directly extend the circuit ourselves, after
             # determining the next relay using the client's path selection
             # determining the next relay using the client's path selection
             # key in conjunction with our own
             # 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 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.")
             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(
             newcirchandler.replace_celltype_handler(
                     SinglePassCreatedCircuitCell,
                     SinglePassCreatedCircuitCell,
                     SinglePassCreatedRelayHandler(ntorreply, next_hop,
                     SinglePassCreatedRelayHandler(ntorreply, next_hop,
-                        vrf_output, msg.ttl))
+                        vrf_output, enckey))
 
 
             # Send the next create message to the next hop
             # Send the next create message to the next hop
             nexthopchannel.send_msg(SinglePassCreateCircuitMsg(newcircid,
             nexthopchannel.send_msg(SinglePassCreateCircuitMsg(newcircid,
                 blinded_client_encr_key, blinded_client_path_selection_key,
                 blinded_client_encr_key, blinded_client_path_selection_key,
                 msg.ttl-1))
                 msg.ttl-1))
+
+            # Now set up the crypto
+            circhandler.add_crypt_layer(enckey, deckey)
         else:
         else:
             return super().received_msg(msg, peeraddr, channel)
             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, end=True)
         network.thenetwork.wantepochticks(self, True)
         network.thenetwork.wantepochticks(self, True)
 
 
+        # Create the path selection key for Single-Pass Walking Onions
         if network.thenetwork.womode == network.WOMode.SINGLEPASS:
         if network.thenetwork.womode == network.WOMode.SINGLEPASS:
             self.path_selection_key = nacl.public.PrivateKey.generate()
             self.path_selection_key = nacl.public.PrivateKey.generate()
+            self.perfstats.keygens += 1
         else:
         else:
             self.path_selection_key = None
             self.path_selection_key = None
 
 
@@ -1098,6 +1115,10 @@ class Relay(network.Server):
         self.channelmgr.get_consensus()
         self.channelmgr.get_consensus()
 
 
     def newepoch(self, epoch):
     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()
         self.uploaddesc()
 
 
     def uploaddesc(self, upload=True):
     def uploaddesc(self, upload=True):
@@ -1112,7 +1133,7 @@ class Relay(network.Server):
         descdict["flags"] = self.flags
         descdict["flags"] = self.flags
 
 
         if network.thenetwork.womode == network.WOMode.SINGLEPASS:
         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 = dirauth.RelayDescriptor(descdict)
         desc.sign(self.idkey, self.perfstats)
         desc.sign(self.idkey, self.perfstats)