[tor-commits] [stem/master] Supporting bridge descriptor's router-digest field

atagar at torproject.org atagar at torproject.org
Wed May 23 17:21:01 UTC 2012


commit e7e03d2f61d6dcc7bc5e5ad4dee91c37a814ee16
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed May 23 09:52:23 2012 -0700

    Supporting bridge descriptor's router-digest field
    
    Scrubbed bridge server and extra-info descriptors will soon have a
    'router-digest' line which contains the server digest. This is a value that we
    can't calculate for ourselves because, of course, the descriptor's contents
    have been modified.
    
    I'm flagging this as being a required field. This does not yet parse it in
    extra-info descriptors since it neither has a digest() method nor did I realize
    that bridge extra-info descriptors were special.
---
 stem/descriptor/server_descriptor.py         |   52 ++++++++++++++++++--------
 test/integ/descriptor/data/bridge_descriptor |    1 +
 test/integ/descriptor/server_descriptor.py   |    1 +
 test/unit/descriptor/server_descriptor.py    |   41 ++++++++++++++++++++-
 4 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 9a91d60..0014ff5 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -11,13 +11,13 @@ etc). This information is provided from a few sources...
 parse_file - Iterates over the server descriptors in a file.
 ServerDescriptor - Tor server descriptor.
   |  |- RelayDescriptor - Server descriptor for a relay.
-  |  |  |- is_valid - checks the signature against the descriptor content
-  |  |  +- digest - calculates the digest value for our content
+  |  |  +- is_valid - checks the signature against the descriptor content
   |  |
   |  +- BridgeDescriptor - Scrubbed server descriptor for a bridge.
   |     |- is_scrubbed - checks if our content has been properly scrubbed
   |     +- get_scrubbing_issues - description of issues with our scrubbing
   |
+  |- digest - calculates the digest value for our content
   |- get_unrecognized_lines - lines with unrecognized content
   |- get_annotations - dictionary of content prior to the descriptor entry
   +- get_annotation_lines - lines that provided the annotations
@@ -248,6 +248,21 @@ class ServerDescriptor(stem.descriptor.Descriptor):
     self._parse(entries, validate)
     if validate: self._check_constraints(entries, first_keyword, last_keyword)
   
+  def digest(self):
+    """
+    Provides the base64 encoded sha1 of our content. This value is part of the
+    server descriptor entry for this relay.
+    
+    Note that network status entries exclude the padding, so you'll need to add
+    a '=' to it so they'll match...
+    https://en.wikipedia.org/wiki/Base64#Padding
+    
+    Returns:
+      str with the digest value for this server descriptor
+    """
+    
+    raise NotImplementedError("Unsupported Operation: this should be implemented by the ServerDescriptor subclass")
+  
   def get_unrecognized_lines(self):
     return list(self._unrecognized_lines)
   
@@ -566,18 +581,6 @@ class RelayDescriptor(ServerDescriptor):
     raise NotImplementedError # TODO: implement
   
   def digest(self):
-    """
-    Provides the base64 encoded sha1 of our content. This value is part of the
-    server descriptor entry for this relay.
-    
-    Note that network status entries exclude the padding, so you'll need to add
-    a '=' to it so they'll match...
-    https://en.wikipedia.org/wiki/Base64#Padding
-    
-    Returns:
-      str with the digest value for this server descriptor
-    """
-    
     if self._digest is None:
       # our digest is calculated from everything except our signature
       raw_content, ending = str(self), "\nrouter-signature\n"
@@ -642,15 +645,28 @@ class BridgeDescriptor(ServerDescriptor):
   
   def __init__(self, raw_contents, validate = True, annotations = None):
     self.address_alt = []
+    self._digest = None
     self._scrubbing_issues = None
     ServerDescriptor.__init__(self, raw_contents, validate, annotations)
   
+  def digest(self):
+    return self._digest
+  
   def _parse(self, entries, validate):
     entries = dict(entries)
     
     # handles fields only in bridge descriptors
     for keyword, values in entries.items():
-      if keyword == "or-address":
+      value, block_contents = values[0]
+      line = "%s %s" % (keyword, value)
+      
+      if keyword == "router-digest":
+        if validate and not re.match("^[0-9a-fA-F]{40}$", value):
+          raise ValueError("Router digest line had an invalid sha1 digest: %s" % line)
+        
+        self._digest = value
+        del entries["router-digest"]
+      elif keyword == "or-address":
         or_address_entries = [value for (value, _) in values]
         
         for entry in or_address_entries:
@@ -741,7 +757,11 @@ class BridgeDescriptor(ServerDescriptor):
       "router-signature",
     )
     
-    return filter(lambda e: not e in excluded_fields, REQUIRED_FIELDS)
+    included_fields = (
+      "router-digest",
+    )
+    
+    return included_fields + filter(lambda e: not e in excluded_fields, REQUIRED_FIELDS)
   
   def _single_fields(self):
     return self._required_fields() + SINGLE_FIELDS
diff --git a/test/integ/descriptor/data/bridge_descriptor b/test/integ/descriptor/data/bridge_descriptor
index 07dffe2..b7dbd92 100644
--- a/test/integ/descriptor/data/bridge_descriptor
+++ b/test/integ/descriptor/data/bridge_descriptor
@@ -1,5 +1,6 @@
 @type bridge-server-descriptor 1.0
 router Unnamed 10.45.227.253 9001 0 0
+router-digest 006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4
 or-address [fd9f:2e19:3bcf::02:9970]:9001
 platform Tor 0.2.3.12-alpha (git-800942b4176ca31c) on Linux x86_64
 opt protocols Link 1 2 Circuit 1
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index afe3c93..180e82c 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -307,5 +307,6 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     self.assertEquals(819200, desc.burst_bandwidth)
     self.assertEquals(5120, desc.observed_bandwidth)
     self.assertEquals(["reject *:*"], desc.exit_policy)
+    self.assertEquals("006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4", desc.digest())
     self.assertEquals([], desc.get_unrecognized_lines())
 
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index e381318..0eb4891 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -27,6 +27,7 @@ RELAY_DESCRIPTOR_ATTR = (
 
 BRIDGE_DESCRIPTOR_ATTR = (
   ("router", "Unnamed 10.45.227.253 9001 0 0"),
+  ("router-digest", "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"),
   ("published", "2012-03-22 17:34:38"),
   ("bandwidth", "409600 819200 5120"),
   ("reject", "*:*"),
@@ -319,6 +320,7 @@ class TestServerDescriptor(unittest.TestCase):
     self.assertEquals("Unnamed", desc.nickname)
     self.assertEquals("10.45.227.253", desc.address)
     self.assertEquals(None, desc.fingerprint)
+    self.assertEquals("006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4", desc.digest())
     
     # check that we don't have crypto fields
     self.assertRaises(AttributeError, getattr, desc, "onion_key")
@@ -352,10 +354,47 @@ class TestServerDescriptor(unittest.TestCase):
     its unsanatized content.
     """
     
-    desc_text = _make_descriptor()
+    desc_text = _make_descriptor({"router-digest": "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"})
     desc = BridgeDescriptor(desc_text)
     self.assertFalse(desc.is_scrubbed())
   
+  def test_router_digest(self):
+    """
+    Constructs with a router-digest line with both valid and invalid contents.
+    """
+    
+    # checks with valid content
+    
+    router_digest = "068A2E28D4C934D9490303B7A645BA068DCA0504"
+    desc_text = _make_descriptor({"router-digest": router_digest}, is_bridge = True)
+    desc = BridgeDescriptor(desc_text)
+    self.assertEquals(router_digest, desc.digest())
+    
+    # checks when missing
+    
+    desc_text = _make_descriptor(exclude = ["router-digest"], is_bridge = True)
+    self.assertRaises(ValueError, BridgeDescriptor, desc_text)
+    
+    # check that we can still construct it without validation
+    desc = BridgeDescriptor(desc_text, validate = False)
+    self.assertEquals(None, desc.digest())
+    
+    # checks with invalid content
+    
+    test_values = (
+      "",
+      "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB44",
+      "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB",
+      "006FD96BA35E7785A6A3B8B75FE2E2435A13BDBH",
+    )
+    
+    for value in test_values:
+      desc_text = _make_descriptor({"router-digest": value}, is_bridge = True)
+      self.assertRaises(ValueError, BridgeDescriptor, desc_text)
+      
+      desc = BridgeDescriptor(desc_text, validate = False)
+      self.assertEquals(value, desc.digest())
+  
   def test_or_address_v4(self):
     """
     Constructs a bridge descriptor with a sanatized IPv4 or-address entry.





More information about the tor-commits mailing list