commit 373d56f8fae1521a05f4983060ef50dd43417bd5 Author: Damian Johnson atagar@torproject.org Date: Wed Aug 26 09:38:10 2015 -0700
Support multiple 'id' lines in microdescriptors
With the ed25519 additions the 'id' lines of microdescriptors can now appear multiple times...
https://gitweb.torproject.org/torspec.git/commit/?id=09ff9e2 --- docs/change_log.rst | 1 + stem/descriptor/microdescriptor.py | 44 +++++++++++++++++++++++-------- stem/descriptor/server_descriptor.py | 6 ++--- test/unit/descriptor/microdescriptor.py | 28 ++++++++++++++++++++ 4 files changed, 65 insertions(+), 14 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst index 1f542ad..6de73f4 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -56,6 +56,7 @@ The following are only available within Stem's `git repository * Support for ed25519 descriptor fields (:spec:`5a79d67`) * Server descriptor validation fails with 'extra-info-digest line had an invalid value' from additions in proposal 228 (:trac:`16227`) * :class:`~stem.descriptor.server_descriptor.BridgeDescriptor` now has 'ntor_onion_key' like its unsanitized counterparts + * Replaced the :class:`~stem.descriptor.microdescriptor.Microdescriptor` identifier and identifier_type attributes with an identifiers hash since it can now appear multiple times (:spec:`09ff9e2`)
* **Website**
diff --git a/stem/descriptor/microdescriptor.py b/stem/descriptor/microdescriptor.py index 1362147..d626101 100644 --- a/stem/descriptor/microdescriptor.py +++ b/stem/descriptor/microdescriptor.py @@ -73,6 +73,7 @@ from stem.descriptor import ( _get_descriptor_components, _read_until_keywords, _value, + _values, _parse_simple_line, _parse_key_block, ) @@ -159,14 +160,24 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
def _parse_id_line(descriptor, entries): - value = _value('id', entries) - value_comp = value.split() + identities = {}
- if len(value_comp) >= 2: - descriptor.identifier_type = value_comp[0] - descriptor.identifier = value_comp[1] - else: - raise ValueError("'id' lines should contain both the key type and digest: id %s" % value) + for entry in _values('id', entries): + entry_comp = entry.split() + + if len(entry_comp) >= 2: + key_type, key_value = entry_comp[0], entry_comp[1] + + if key_type in identities: + raise ValueError("There can only be one 'id' line per a key type, but '%s' appeared multiple times" % key_type) + + descriptor.identifier_type = key_type + descriptor.identifier = key_value + identities[key_type] = key_value + else: + raise ValueError("'id' lines should contain both the key type and digest: id %s" % entry) + + descriptor.identifiers = identities
_parse_digest = lambda descriptor, entries: setattr(descriptor, 'digest', hashlib.sha256(descriptor.get_bytes()).hexdigest().upper()) @@ -192,13 +203,23 @@ class Microdescriptor(Descriptor): :var list family: ***** nicknames or fingerprints of declared family :var stem.exit_policy.MicroExitPolicy exit_policy: ***** relay's exit policy :var stem.exit_policy.MicroExitPolicy exit_policy_v6: ***** exit policy for IPv6 - :var str identifier_type: identity digest key type - :var str identifier: base64 encoded identity digest, this is only used for collision prevention (:trac:`11743`) + :var hash identifiers: mapping of key types (like rsa1024 or ed25519) to + their base64 encoded identity, this is only used for collision prevention + (:trac:`11743`) + + :var str identifier: base64 encoded identity digest (**deprecated**, use + identifiers instead) + :var str identifier_type: identity digest key type (**deprecated**, use + identifiers instead)
***** attribute is required when we're parsed with validation
.. versionchanged:: 1.1.0 Added the identifier and identifier_type attributes. + + .. versionchanged:: 1.5.0 + Added the identifiers attribute, and deprecated identifier and + identifier_type since the field can now appear multiple times. """
ATTRIBUTES = { @@ -208,8 +229,9 @@ class Microdescriptor(Descriptor): 'family': ([], _parse_family_line), 'exit_policy': (stem.exit_policy.MicroExitPolicy('reject 1-65535'), _parse_p_line), 'exit_policy_v6': (None, _parse_p6_line), - 'identifier_type': (None, _parse_id_line), - 'identifier': (None, _parse_id_line), + 'identifier_type': (None, _parse_id_line), # deprecated in favor of identifiers + 'identifier': (None, _parse_id_line), # deprecated in favor of identifiers + 'identifiers': ({}, _parse_id_line), 'digest': (None, _parse_digest), }
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index be40f62..07b224a 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -420,7 +420,7 @@ class ServerDescriptor(Descriptor):
:var str address: ***** IPv4 address of the relay :var int or_port: ***** port used for relaying - :var int socks_port: ***** port used as client (deprecated, always **None**) + :var int socks_port: ***** port used as client (**deprecated**, always **None**) :var int dir_port: ***** port used for descriptor mirroring
:var bytes platform: line with operating system and tor version @@ -442,13 +442,13 @@ class ServerDescriptor(Descriptor): :var bool allow_single_hop_exits: ***** flag if single hop exiting is allowed :var bool extra_info_cache: ***** flag if a mirror for extra-info documents :var str extra_info_digest: upper-case hex encoded digest of our extra-info document - :var bool eventdns: flag for evdns backend (deprecated, always unset) + :var bool eventdns: flag for evdns backend (**deprecated**, always unset) :var str ntor_onion_key: base64 key used to encrypt EXTEND in the ntor protocol :var list or_addresses: ***** alternative for our address/or_port attributes, each entry is a tuple of the form (address (**str**), port (**int**), is_ipv6 (**bool**))
- Deprecated, moved to extra-info descriptor... + **Deprecated**, moved to extra-info descriptor...
:var datetime read_history_end: end of the sampling interval :var int read_history_interval: seconds per interval diff --git a/test/unit/descriptor/microdescriptor.py b/test/unit/descriptor/microdescriptor.py index 598f58e..f21e518 100644 --- a/test/unit/descriptor/microdescriptor.py +++ b/test/unit/descriptor/microdescriptor.py @@ -95,6 +95,7 @@ class TestMicrodescriptor(unittest.TestCase): self.assertEqual([], desc.family) self.assertEqual(stem.exit_policy.MicroExitPolicy('reject 1-65535'), desc.exit_policy) self.assertEqual(None, desc.exit_policy_v6) + self.assertEqual({}, desc.identifiers) self.assertEqual(None, desc.identifier_type) self.assertEqual(None, desc.identifier) self.assertEqual([], desc.get_unrecognized_lines()) @@ -170,5 +171,32 @@ class TestMicrodescriptor(unittest.TestCase): """
desc = get_microdescriptor({'id': 'rsa1024 Cd47okjCHD83YGzThGBDptXs9Z4'}) + self.assertEqual({'rsa1024': 'Cd47okjCHD83YGzThGBDptXs9Z4'}, desc.identifiers) self.assertEqual('rsa1024', desc.identifier_type) self.assertEqual('Cd47okjCHD83YGzThGBDptXs9Z4', desc.identifier) + + # check when there's multiple key types + + desc_text = b'\n'.join((get_microdescriptor(content = True), + b'id rsa1024 Cd47okjCHD83YGzThGBDptXs9Z4', + b'id ed25519 50f6ddbecdc848dcc6b818b14d1')) + + desc = Microdescriptor(desc_text, validate = True) + self.assertEqual({'rsa1024': 'Cd47okjCHD83YGzThGBDptXs9Z4', 'ed25519': '50f6ddbecdc848dcc6b818b14d1'}, desc.identifiers) + self.assertEqual('ed25519', desc.identifier_type) + self.assertEqual('50f6ddbecdc848dcc6b818b14d1', desc.identifier) + + # check when there's conflicting keys + + desc_text = b'\n'.join((get_microdescriptor(content = True), + b'id rsa1024 Cd47okjCHD83YGzThGBDptXs9Z4', + b'id rsa1024 50f6ddbecdc848dcc6b818b14d1')) + + desc = Microdescriptor(desc_text) + self.assertEqual({}, desc.identifiers) + + try: + Microdescriptor(desc_text, validate = True) + self.fail('constructor validation should fail') + except ValueError as exc: + self.assertEqual("There can only be one 'id' line per a key type, but 'rsa1024' appeared multiple times", str(exc))
tor-commits@lists.torproject.org