[tor-commits] [stem/master] Document v3 hidden service support

atagar at torproject.org atagar at torproject.org
Sun Mar 4 00:03:57 UTC 2018


commit 53e73ad8ac9ad8af54ca64d57895b2b35ad9ee5f
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Mar 3 15:58:35 2018 -0800

    Document v3 hidden service support
    
    Huh, didn't expect that. Unless I'm missing something there isn't much for us
    to do in Stem for v3 Hidden Service support. We already let users specify their
    key type so callers could already pick the protocol version to use...
    
      # To create a v2 hidden service...
    
      controller.create_ephemeral_hidden_service(443, key_content = 'RSA1024')
    
      # To create a v3 hidden service...
    
      controller.create_ephemeral_hidden_service(443, key_content = 'ED25519-V3')
    
      # To create a hidden service based on the HiddenServiceVersion in your
      # torrc...
    
      controller.create_ephemeral_hidden_service(443)
    
    Pretty much all there was to do was document and add an integ test. Neat. :P
    
      https://trac.torproject.org/projects/tor/ticket/25124
      https://gitweb.torproject.org/torspec.git/commit/?id=6bd0a69
---
 docs/change_log.rst              |  1 +
 stem/control.py                  | 13 ++++++--
 stem/interpreter/settings.cfg    |  2 ++
 stem/response/events.py          |  6 +++-
 stem/version.py                  |  2 ++
 test/integ/control/controller.py | 67 ++++++++++++++++++++++++++++++++++------
 6 files changed, 77 insertions(+), 14 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index 4a5e927b..bb42c982 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -46,6 +46,7 @@ The following are only available within Stem's `git repository
 
  * **Controller**
 
+  * Documented v3 hidden service support (:trac:`25124`, :spec:`6bd0a69`)
   * Added support for limiting the maximum number of streams to :func:`~stem.control.Controller.create_ephemeral_hidden_service` (:spec:`2fcb1c2`)
   * Stacktrace if :func:`stem.connection.connect` had a string port argument
   * Replaced socket's :func:`~stem.socket.ControlPort.get_address`, :func:`~stem.socket.ControlPort.get_port`, and :func:`~stem.socket.ControlSocketFile.get_socket_path` with attributes
diff --git a/stem/control.py b/stem/control.py
index 04cd0246..5ccc318f 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -1987,6 +1987,8 @@ class Controller(BaseController):
     If **await_result** is **True** then this blocks until we either receive
     the descriptor or the request fails. If **False** this returns right away.
 
+    **This method only supports v2 hidden services, not v3.** (:trac:`25417`)
+
     .. versionadded:: 1.4.0
 
     :param str address: address of the hidden service descriptor, the '.onion' suffix is optional
@@ -2853,6 +2855,11 @@ class Controller(BaseController):
         'bob': 'vGnNRpWYiMBFTWD2gbBlcA',
       })
 
+    To create a **version 3** service simply specify **ED25519-V3** as the
+    key_type, and to create a **version 2** service use **RSA1024**. The
+    default version of newly created hidden services is based on the
+    **HiddenServiceVersion** value in your torrc.
+
     .. versionadded:: 1.4.0
 
     .. versionchanged:: 1.5.0
@@ -2869,10 +2876,10 @@ class Controller(BaseController):
     :param int,list,dict ports: hidden service port(s) or mapping of hidden
       service ports to their targets
     :param str key_type: type of key being provided, generates a new key if
-      'NEW' (options are: **NEW** and **RSA1024**)
+      'NEW' (options are: **NEW**, **RSA1024**, and **ED25519-V3**)
     :param str key_content: key for the service to use or type of key to be
-      generated (options when **key_type** is **NEW** are **BEST** and
-      **RSA1024**)
+      generated (options when **key_type** is **NEW** are **BEST**,
+      **RSA1024**, and **ED25519-V3**)
     :param bool discard_key: avoid providing the key back in our response
     :param bool detached: continue this hidden service even after this control
       connection is closed if **True**
diff --git a/stem/interpreter/settings.cfg b/stem/interpreter/settings.cfg
index cc8da378..af96d599 100644
--- a/stem/interpreter/settings.cfg
+++ b/stem/interpreter/settings.cfg
@@ -323,7 +323,9 @@ autocomplete AUTHCHALLENGE
 autocomplete DROPGUARDS
 autocomplete ADD_ONION NEW:BEST
 autocomplete ADD_ONION NEW:RSA1024
+autocomplete ADD_ONION NEW:ED25519-V3
 autocomplete ADD_ONION RSA1024:
+autocomplete ADD_ONION ED25519-V3:
 autocomplete DEL_ONION
 autocomplete HSFETCH
 autocomplete HSPOST
diff --git a/stem/response/events.py b/stem/response/events.py
index e8c8a6e0..6d62fbb8 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -652,6 +652,9 @@ class HSDescEvent(Event):
   .. versionchanged:: 1.5.0
      Added the replica attribute.
 
+  .. versionchanged:: 1.7.0
+     Added the index attribute.
+
   :var stem.HSDescAction action: what is happening with the descriptor
   :var str address: hidden service address
   :var stem.HSAuth authentication: service's authentication method
@@ -661,11 +664,12 @@ class HSDescEvent(Event):
   :var str descriptor_id: descriptor identifier
   :var stem.HSDescReason reason: reason the descriptor failed to be fetched
   :var int replica: replica number the descriptor involves
+  :var str index: computed index of the HSDir the descriptor was uploaded to or fetched from
   """
 
   _VERSION_ADDED = stem.version.Requirement.EVENT_HS_DESC
   _POSITIONAL_ARGS = ('action', 'address', 'authentication', 'directory', 'descriptor_id')
-  _KEYWORD_ARGS = {'REASON': 'reason', 'REPLICA': 'replica'}
+  _KEYWORD_ARGS = {'REASON': 'reason', 'REPLICA': 'replica', 'HSDIR_INDEX': 'index'}
 
   def _parse(self):
     self.directory_fingerprint = None
diff --git a/stem/version.py b/stem/version.py
index 03eb549c..9de2f1a5 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -61,6 +61,7 @@ easily parsed and compared, for instance...
   **FEATURE_VERBOSE_NAMES**             'VERBOSE_NAMES' optional feature
   **GETINFO_CONFIG_TEXT**               'GETINFO config-text' query
   **GETINFO_GEOIP_AVAILABLE**           'GETINFO ip-to-country/ipv4-available' query and its ipv6 counterpart
+  **HIDDEN_SERVICE_V3**                 Support for v3 hidden services
   **HSFETCH**                           HSFETCH requests
   **HSPOST**                            HSPOST requests
   **ADD_ONION**                         ADD_ONION and DEL_ONION requests
@@ -378,6 +379,7 @@ Requirement = stem.util.enum.Enum(
   ('FEATURE_VERBOSE_NAMES', Version('0.2.2.1-alpha')),
   ('GETINFO_CONFIG_TEXT', Version('0.2.2.7-alpha')),
   ('GETINFO_GEOIP_AVAILABLE', Version('0.3.2.1-alpha')),
+  ('HIDDEN_SERVICE_V3', Version('0.3.3.1-alpha')),
   ('HSFETCH', Version('0.2.7.1-alpha')),
   ('HSPOST', Version('0.2.7.1-alpha')),
   ('ADD_ONION', Version('0.2.7.1-alpha')),
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 16061eee..a3b8af38 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -563,24 +563,71 @@ class TestController(unittest.TestCase):
 
   @test.require.controller
   @test.require.version(Requirement.ADD_ONION)
-  def test_with_ephemeral_hidden_services(self):
+  def test_with_invalid_ephemeral_hidden_service_port(self):
+    with test.runner.get_runner().get_tor_controller() as controller:
+      for ports in (4567890, [4567, 4567890], {4567: '-:4567'}):
+        exc_msg = "ADD_ONION response didn't have an OK status: Invalid VIRTPORT/TARGET"
+        self.assertRaisesRegexp(stem.ProtocolError, exc_msg, controller.create_ephemeral_hidden_service, ports)
+
+  @test.require.controller
+  @test.require.version(Requirement.ADD_ONION)
+  def test_ephemeral_hidden_services_v2(self):
     """
-    Exercises creating ephemeral hidden services and methods when they're
-    present.
+    Exercises creating v2 ephemeral hidden services.
     """
 
     runner = test.runner.get_runner()
 
     with runner.get_tor_controller() as controller:
-      # try creating a service with an invalid ports
+      response = controller.create_ephemeral_hidden_service(4567, key_content = 'RSA1024')
+      self.assertEqual([response.service_id], controller.list_ephemeral_hidden_services())
+      self.assertEqual(812, len(response.private_key))
+      self.assertEqual('RSA1024', response.private_key_type)
+      self.assertEqual({}, response.client_auth)
 
-      for ports in (4567890, [4567, 4567890], {4567: '-:4567'}):
-        exc_msg = "ADD_ONION response didn't have an OK status: Invalid VIRTPORT/TARGET"
-        self.assertRaisesRegexp(stem.ProtocolError, exc_msg, controller.create_ephemeral_hidden_service, ports)
+      # drop the service
+
+      self.assertEqual(True, controller.remove_ephemeral_hidden_service(response.service_id))
+      self.assertEqual([], controller.list_ephemeral_hidden_services())
+
+      # recreate the service with the same private key
 
-      response = controller.create_ephemeral_hidden_service(4567)
+      recreate_response = controller.create_ephemeral_hidden_service(4567, key_type = response.private_key_type, key_content = response.private_key)
       self.assertEqual([response.service_id], controller.list_ephemeral_hidden_services())
-      self.assertTrue(response.private_key is not None)
+      self.assertEqual(response.service_id, recreate_response.service_id)
+
+      # the response only includes the private key when making a new one
+
+      self.assertEqual(None, recreate_response.private_key)
+      self.assertEqual(None, recreate_response.private_key_type)
+
+      # create a service where we never see the private key
+
+      response = controller.create_ephemeral_hidden_service(4568, key_content = 'RSA1024', discard_key = True)
+      self.assertTrue(response.service_id in controller.list_ephemeral_hidden_services())
+      self.assertEqual(None, response.private_key)
+      self.assertEqual(None, response.private_key_type)
+
+      # other controllers shouldn't be able to see these hidden services
+
+      with runner.get_tor_controller() as second_controller:
+        self.assertEqual(2, len(controller.list_ephemeral_hidden_services()))
+        self.assertEqual(0, len(second_controller.list_ephemeral_hidden_services()))
+
+  @test.require.controller
+  @test.require.version(Requirement.HIDDEN_SERVICE_V3)
+  def test_ephemeral_hidden_services_v3(self):
+    """
+    Exercises creating v3 ephemeral hidden services.
+    """
+
+    runner = test.runner.get_runner()
+
+    with runner.get_tor_controller() as controller:
+      response = controller.create_ephemeral_hidden_service(4567, key_content = 'ED25519-V3')
+      self.assertEqual([response.service_id], controller.list_ephemeral_hidden_services())
+      self.assertEqual(88, len(response.private_key))
+      self.assertEqual('ED25519-V3', response.private_key_type)
       self.assertEqual({}, response.client_auth)
 
       # drop the service
@@ -601,7 +648,7 @@ class TestController(unittest.TestCase):
 
       # create a service where we never see the private key
 
-      response = controller.create_ephemeral_hidden_service(4568, discard_key = True)
+      response = controller.create_ephemeral_hidden_service(4568, key_content = 'ED25519-V3', discard_key = True)
       self.assertTrue(response.service_id in controller.list_ephemeral_hidden_services())
       self.assertEqual(None, response.private_key)
       self.assertEqual(None, response.private_key_type)



More information about the tor-commits mailing list