[tor-commits] [stem/master] Validate mandatory fields are present
atagar at torproject.org
atagar at torproject.org
Sun Aug 25 00:20:44 UTC 2019
commit 5b1fc94f6cb6719ff9bc2ab2c3c5620ac158d08b
Author: Damian Johnson <atagar at torproject.org>
Date: Sat Aug 24 16:21:24 2019 -0700
Validate mandatory fields are present
---
docs/change_log.rst | 3 ++-
docs/contents.rst | 1 +
docs/tutorials/mirror_mirror_on_the_wall.rst | 2 +-
stem/descriptor/hidden_service.py | 32 +++++++++++++++++++++++-----
test/unit/descriptor/hidden_service_v2.py | 4 ++--
test/unit/descriptor/hidden_service_v3.py | 25 +++++++++++++++++++++-
6 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index d9c6bfa3..c5b5051e 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -60,6 +60,7 @@ The following are only available within Stem's `git repository
* **Descriptors**
* Added the `stem.descriptor.collector <api/descriptor/collector.html>`_ module (:trac:`17979`)
+ * Added `v3 hidden service descriptor support <api/descriptor/hidden_service.html>`_ (:trac:`31369`)
* `Bandwidth file support <api/descriptor/bandwidth_file.html>`_ (:trac:`29056`)
* `stem.descriptor.remote <api/descriptor/remote.html>`_ methods now raise :class:`stem.DownloadFailed`
* Check Ed25519 validity though the cryptography module rather than PyNaCl (:trac:`22022`)
@@ -364,7 +365,7 @@ And last, Stem also now runs directly under both python2 and python3 without a
* **Descriptors**
* Lazy-loading descriptors, improving performance by 25-70% depending on what type it is (:trac:`14011`)
- * Added `support for hidden service descriptors <api/descriptor/hidden_service_descriptor.html>`_ (:trac:`15004`)
+ * Added `support for hidden service descriptors <api/descriptor/hidden_service.html>`_ (:trac:`15004`)
* When reading sanitised bridge descriptors (server or extrainfo), :func:`~stem.descriptor.__init__.parse_file` treated the whole file as a single descriptor
* The :class:`~stem.descriptor.networkstatus.DirectoryAuthority` 'fingerprint' attribute was actually its 'v3ident'
* Added consensus' new package attribute (:spec:`ab64534`)
diff --git a/docs/contents.rst b/docs/contents.rst
index 98e80a5f..87e75220 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -14,6 +14,7 @@ Contents
tutorials/down_the_rabbit_hole
tutorials/double_double_toil_and_trouble
+ tutorials/examples/bandwidth_stats
tutorials/examples/check_digests
tutorials/examples/compare_flags
tutorials/examples/download_descriptor
diff --git a/docs/tutorials/mirror_mirror_on_the_wall.rst b/docs/tutorials/mirror_mirror_on_the_wall.rst
index 699625e4..f16df19b 100644
--- a/docs/tutorials/mirror_mirror_on_the_wall.rst
+++ b/docs/tutorials/mirror_mirror_on_the_wall.rst
@@ -34,7 +34,7 @@ Descriptor Type
`Microdescriptor <../api/descriptor/microdescriptor.html>`_ Minimalistic document that just includes the information necessary for Tor clients to work.
`Network Status Document <../api/descriptor/networkstatus.html>`_ Though Tor relays are decentralized, the directories that track the overall network are not. These central points are called **directory authorities**, and every hour they publish a document called a **consensus** (aka, network status document). The consensus in turn is made up of **router status entries**.
`Router Status Entry <../api/descriptor/router_status_entry.html>`_ Relay information provided by the directory authorities including flags, heuristics used for relay selection, etc.
-`Hidden Service Descriptor <../api/descriptor/hidden_service.html>`_ Information pertaining to a `Hidden Service <https://www.torproject.org/docs/hidden-services.html.en>`_. These can only be `queried through the tor process <over_the_river.html#hidden-service-descriptors>`_.
+`Hidden Service Descriptor <../api/descriptor/hidden_service.html>`_ Information pertaining to a `Hidden Service <https://www.torproject.org/docs/hidden-services.html.en>`_. These can only be `queried through the tor process <over_the_river.html#hidden-service-descriptors>`_.
================================================================================ ===========
.. _where-do-descriptors-come-from:
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 1ab450fd..52e1b0b1 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -2,15 +2,17 @@
# See LICENSE for licensing information
"""
-Parsing for Tor hidden service descriptors as described in Tor's `rend-spec
-<https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt>`_.
+Parsing for Tor hidden service descriptors as described in Tor's `version 2
+<https://gitweb.torproject.org/torspec.git/tree/rend-spec-v2.txt>`_ and
+`version 3 <https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt>`_
+rend-spec.
Unlike other descriptor types these describe a hidden service rather than a
relay. They're created by the service, and can only be fetched via relays with
the HSDir flag.
These are only available through the Controller's
-:func:`~stem.control.get_hidden_service_descriptor` method.
+:func:`~stem.control.Controller.get_hidden_service_descriptor` method.
**Module Overview:**
@@ -54,7 +56,7 @@ if stem.prereq._is_lru_cache_available():
else:
from stem.util.lru_cache import lru_cache
-REQUIRED_FIELDS = (
+REQUIRED_V2_FIELDS = (
'rendezvous-service-descriptor',
'version',
'permanent-key',
@@ -64,6 +66,15 @@ REQUIRED_FIELDS = (
'signature',
)
+REQUIRED_V3_FIELDS = (
+ 'hs-descriptor',
+ 'descriptor-lifetime',
+ 'descriptor-signing-key-cert',
+ 'revision-counter',
+ 'superencrypted',
+ 'signature',
+)
+
INTRODUCTION_POINTS_ATTR = {
'identifier': None,
'address': None,
@@ -284,7 +295,7 @@ class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
entries = _descriptor_components(raw_contents, validate, non_ascii_fields = ('introduction-points'))
if validate:
- for keyword in REQUIRED_FIELDS:
+ for keyword in REQUIRED_V2_FIELDS:
if keyword not in entries:
raise ValueError("Hidden service descriptor must have a '%s' entry" % keyword)
elif keyword in entries and len(entries[keyword]) > 1:
@@ -520,6 +531,17 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
entries = _descriptor_components(raw_contents, validate)
if validate:
+ for keyword in REQUIRED_V3_FIELDS:
+ if keyword not in entries:
+ raise ValueError("Hidden service descriptor must have a '%s' entry" % keyword)
+ elif keyword in entries and len(entries[keyword]) > 1:
+ raise ValueError("The '%s' entry can only appear once in a hidden service descriptor" % keyword)
+
+ if 'hs-descriptor' != list(entries.keys())[0]:
+ raise ValueError("Hidden service descriptor must start with a 'hs-descriptor' entry")
+ elif 'signature' != list(entries.keys())[-1]:
+ raise ValueError("Hidden service descriptor must end with a 'signature' entry")
+
self._parse(entries, validate)
else:
self._entries = entries
diff --git a/test/unit/descriptor/hidden_service_v2.py b/test/unit/descriptor/hidden_service_v2.py
index ac64fa19..5191775e 100644
--- a/test/unit/descriptor/hidden_service_v2.py
+++ b/test/unit/descriptor/hidden_service_v2.py
@@ -11,7 +11,7 @@ import stem.prereq
import test.require
from stem.descriptor.hidden_service import (
- REQUIRED_FIELDS,
+ REQUIRED_V2_FIELDS,
DecryptionFailure,
HiddenServiceDescriptorV2,
)
@@ -468,7 +468,7 @@ class TestHiddenServiceDescriptorV2(unittest.TestCase):
'signature': 'signature',
}
- for line in REQUIRED_FIELDS:
+ for line in REQUIRED_V2_FIELDS:
desc_text = HiddenServiceDescriptorV2.content(exclude = (line,))
expected = [] if line == 'protocol-versions' else None
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index d441c8cc..f6407623 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -7,14 +7,19 @@ import unittest
import stem.descriptor
-from stem.descriptor.hidden_service import HiddenServiceDescriptorV3
+from stem.descriptor.hidden_service import (
+ REQUIRED_V3_FIELDS,
+ HiddenServiceDescriptorV3,
+)
from test.unit.descriptor import (
get_resource,
base_expect_invalid_attr,
+ base_expect_invalid_attr_for_text,
)
expect_invalid_attr = functools.partial(base_expect_invalid_attr, HiddenServiceDescriptorV3, 'version', 3)
+expect_invalid_attr_for_text = functools.partial(base_expect_invalid_attr_for_text, HiddenServiceDescriptorV3, 'version', 3)
EXPECTED_SIGNING_CERT = """\
-----BEGIN ED25519 CERT-----
@@ -43,6 +48,24 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
self.assertTrue('k9uKnDpxhkH0h1h' in desc.superencrypted)
self.assertEqual('wdc7ffr+dPZJ/mIQ1l4WYqNABcmsm6SHW/NL3M3wG7bjjqOJWoPR5TimUXxH52n5Zk0Gc7hl/hz3YYmAx5MvAg', desc.signature)
+ def test_required_fields(self):
+ """
+ Check that we require the mandatory fields.
+ """
+
+ line_to_attr = {
+ 'hs-descriptor': 'version',
+ 'descriptor-lifetime': 'lifetime',
+ 'descriptor-signing-key-cert': 'signing_cert',
+ 'revision-counter': 'revision_counter',
+ 'superencrypted': 'superencrypted',
+ 'signature': 'signature',
+ }
+
+ for line in REQUIRED_V3_FIELDS:
+ desc_text = HiddenServiceDescriptorV3.content(exclude = (line,))
+ expect_invalid_attr_for_text(self, desc_text, line_to_attr[line], None)
+
def test_invalid_version(self):
"""
Checks that our version field expects a numeric value.
More information about the tor-commits
mailing list