commit 850b1cbbc07554ea369b44c89b3bbfdd1b99fa69 Author: Damian Johnson atagar@torproject.org Date: Thu Apr 12 10:53:28 2012 -0700
Unit tests for bridge descriptors and related fixes
Unit testing the new bridge descriptors. This mostly focused on parsing or-address entries and the validation that the class does for scrubbed values. Fixes included having string port values and not checking that or-address addresses were scrubbed. --- stem/descriptor/server_descriptor.py | 10 ++- test/unit/descriptor/server_descriptor.py | 121 ++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 10 deletions(-)
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 52bca94..dfdde30 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -645,7 +645,7 @@ class BridgeDescriptorV3(ServerDescriptorV3): if not validate: break else: raise ValueError("or-address line has malformed ports: %s" % line)
- self.address_alt.append((address, port, is_ipv6)) + self.address_alt.append((address, int(port), is_ipv6))
del entries["or-address"]
@@ -665,6 +665,14 @@ class BridgeDescriptorV3(ServerDescriptorV3): elif self.contact and self.contact != "somebody": raise ValueError("Contact line should be scrubbed to be 'somebody', but instead had '%s'" % self.contact)
+ for address, _, is_ipv6 in self.address_alt: + if not is_ipv6 and not address.startswith("10."): + raise ValueError("or-address line's address should be scrubbed to be '10.x.x.x': %s" % address) + elif is_ipv6 and not address.startswith("fd9f:2e19:3bcf::"): + # TODO: this check isn't quite right because we aren't checking that + # the next grouping of hex digits contains 1-2 digits + raise ValueError("or-address line's address should be scrubbed to be 'fd9f:2e19:3bcf::xx:xxxx': %s" % address) + for line in self.get_unrecognized_lines(): if line.startswith("onion-key "): raise ValueError("Bridge descriptors should have their onion-key scrubbed: %s" % line) diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py index 42325ff..89a28c6 100644 --- a/test/unit/descriptor/server_descriptor.py +++ b/test/unit/descriptor/server_descriptor.py @@ -7,7 +7,7 @@ import StringIO import unittest
import stem.descriptor.server_descriptor -from stem.descriptor.server_descriptor import RelayDescriptorV3 +from stem.descriptor.server_descriptor import RelayDescriptorV3, BridgeDescriptorV3
CRYPTO_BLOB = """ MIGJAoGBAJv5IIWQ+WDWYUdyA/0L8qbIkEVH/cwryZWoIaPAzINfrw1WfNZGtBmg @@ -15,7 +15,7 @@ skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+ WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE= """
-SAMPLE_DESCRIPTOR_ATTR = ( +RELAY_DESCRIPTOR_ATTR = ( ("router", "caerSidi 71.35.133.197 9001 0 0"), ("published", "2012-03-01 17:15:27"), ("bandwidth", "153600 256000 104590"), @@ -25,13 +25,21 @@ SAMPLE_DESCRIPTOR_ATTR = ( ("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB), )
-def _make_descriptor(attr = None, exclude = None): +BRIDGE_DESCRIPTOR_ATTR = ( + ("router", "Unnamed 10.45.227.253 9001 0 0"), + ("published", "2012-03-22 17:34:38"), + ("bandwidth", "409600 819200 5120"), + ("reject", "*:*"), +) + +def _make_descriptor(attr = None, exclude = None, is_bridge = False): """ Constructs a minimal server descriptor with the given attributes.
Arguments: - attr (dict) - keyword/value mappings to be included in the descriptor - exclude (list) - mandatory keywords to exclude from the descriptor + attr (dict) - keyword/value mappings to be included in the descriptor + exclude (list) - mandatory keywords to exclude from the descriptor + is_bridge (bool) - minimal descriptor is for a bridge if True, relay otherwise
Returns: str with customized descriptor content @@ -40,26 +48,35 @@ def _make_descriptor(attr = None, exclude = None): descriptor_lines = [] if attr == None: attr = {} if exclude == None: exclude = [] + desc_attr = BRIDGE_DESCRIPTOR_ATTR if is_bridge else RELAY_DESCRIPTOR_ATTR + attr = dict(attr) # shallow copy since we're destructive
- for keyword, value in SAMPLE_DESCRIPTOR_ATTR: + for keyword, value in desc_attr: if keyword in exclude: continue elif keyword in attr: value = attr[keyword] del attr[keyword]
# if this is the last entry then we should dump in any unused attributes - if keyword == "router-signature": + if not is_bridge and keyword == "router-signature": for attr_keyword, attr_value in attr.items(): descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
descriptor_lines.append("%s %s" % (keyword, value))
+ # bridges don't have a router-signature so simply append any extra attributes + # to the end + if is_bridge: + for attr_keyword, attr_value in attr.items(): + descriptor_lines.append("%s %s" % (attr_keyword, attr_value)) + return "\n".join(descriptor_lines)
class TestServerDescriptor(unittest.TestCase): - def test_minimal_descriptor(self): + def test_minimal_relay_descriptor(self): """ - Basic sanity check that we can parse a descriptor with minimal attributes. + Basic sanity check that we can parse a relay server descriptor with minimal + attributes. """
desc_text = _make_descriptor() @@ -255,6 +272,92 @@ class TestServerDescriptor(unittest.TestCase): self.assertEquals(None, desc.socks_port) self.assertEquals(None, desc.dir_port)
+ def test_minimal_bridge_descriptor(self): + """ + Basic sanity check that we can parse a descriptor with minimal attributes. + """ + + desc_text = _make_descriptor(is_bridge = True) + desc = BridgeDescriptorV3(desc_text) + + self.assertEquals("Unnamed", desc.nickname) + self.assertEquals("10.45.227.253", desc.address) + self.assertEquals(None, desc.fingerprint) + + # check that we don't have crypto fields + self.assertRaises(AttributeError, getattr, desc, "onion_key") + self.assertRaises(AttributeError, getattr, desc, "signing_key") + self.assertRaises(AttributeError, getattr, desc, "signature") + + def test_bridge_unsanitized(self): + """ + Targeted check that individual unsanitized attributes will be detected. + """ + + unsanitized_attr = [ + {"router": "caerSidi 10.45.227.253 9001 0 0"}, + {"router": "Unnamed 75.45.227.253 9001 0 0"}, + {"contact": "Damian"}, + {"or-address": "71.35.133.197:9001"}, + {"or-address": "[12ab:2e19:3bcf::02:9970]:9001"}, + {"onion-key": "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB}, + {"signing-key": "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB}, + {"router-signature": "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB}, + ] + + for attr in unsanitized_attr: + try: + desc_text = _make_descriptor(attr, is_bridge = True) + BridgeDescriptorV3(desc_text) + self.fail("Unsanitized attribute wasn't detected: %s %s" % attr.items()[0]) + except ValueError: pass + + def test_bridge_unsanitized_relay(self): + """ + Checks that parsing a normal relay descriptor as a bridge will fail due to + its unsanatized content. + """ + + desc_text = _make_descriptor() + self.assertRaises(ValueError, BridgeDescriptorV3, desc_text) + + def test_or_address_v4(self): + """ + Constructs a bridge descriptor with a sanatized IPv4 or-address entry. + """ + + desc_text = _make_descriptor({"or-address": "10.45.227.253:9001"}, is_bridge = True) + desc = BridgeDescriptorV3(desc_text) + self.assertEquals([("10.45.227.253", 9001, False)], desc.address_alt) + + def test_or_address_v6(self): + """ + Constructs a bridge descriptor with a sanatized IPv6 or-address entry. + """ + + desc_text = _make_descriptor({"or-address": "[fd9f:2e19:3bcf::02:9970]:9001"}, is_bridge = True) + desc = BridgeDescriptorV3(desc_text) + self.assertEquals([("fd9f:2e19:3bcf::02:9970", 9001, True)], desc.address_alt) + + def test_or_address_multiple(self): + """ + Constructs a bridge descriptor with multiple or-address entries and multiple ports. + """ + + desc_text = "\n".join((_make_descriptor(is_bridge = True), + "or-address 10.45.227.253:9001,9005,80", + "or-address [fd9f:2e19:3bcf::02:9970]:443")) + + expected_address_alt = [ + ("10.45.227.253", 9001, False), + ("10.45.227.253", 9005, False), + ("10.45.227.253", 80, False), + ("fd9f:2e19:3bcf::02:9970", 443, True), + ] + + desc = BridgeDescriptorV3(desc_text) + self.assertEquals(expected_address_alt, desc.address_alt) + def _expect_invalid_attr(self, desc_text, attr = None, expected_value = None): """ Asserts that construction will fail due to desc_text having a malformed