[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