Unverified Commit 2f3bbf64 authored by mig5's avatar mig5
Browse files

Adds ClientAuthV3 support to the controller, for setting Client Auth with ADD_ONION on v3 onions

parent 63a47605
Loading
Loading
Loading
Loading
+21 −4
Original line number Diff line number Diff line
@@ -2889,7 +2889,7 @@ class Controller(BaseController):

    return [r for r in result if r]  # drop any empty responses (GETINFO is blank if unset)

  async def create_ephemeral_hidden_service(self, ports: Union[int, Sequence[int], Mapping[int, str]], key_type: str = 'NEW', key_content: str = 'BEST', discard_key: bool = False, detached: bool = False, await_publication: bool = False, timeout: Optional[float] = None, basic_auth: Optional[Mapping[str, str]] = None, max_streams: Optional[int] = None) -> stem.response.add_onion.AddOnionResponse:
  async def create_ephemeral_hidden_service(self, ports: Union[int, Sequence[int], Mapping[int, str]], key_type: str = 'NEW', key_content: str = 'BEST', discard_key: bool = False, detached: bool = False, await_publication: bool = False, timeout: Optional[float] = None, basic_auth: Optional[Mapping[str, str]] = None, max_streams: Optional[int] = None, client_auth_v3: Optional[str] = None) -> stem.response.add_onion.AddOnionResponse:
    """
    Creates a new hidden service. Unlike
    :func:`~stem.control.Controller.create_hidden_service` this style of
@@ -2940,8 +2940,10 @@ class Controller(BaseController):
      })

    Please note that **basic_auth** only works for legacy (v2) hidden services.
    Version 3 can't enable service authentication through the control protocol
    (:ticket:`tor-40084`).

    To use client auth with a **version 3** service, pass the **client_auth_v3**
    argument. The value must be a base32-encoded public key from a key pair you
    have generated elsewhere.

    To create a **version 3** service simply specify **ED25519-V3** as the
    our key type, and to create a **version 2** service use **RSA1024**. The
@@ -2958,6 +2960,7 @@ class Controller(BaseController):

      print('service established at %s.onion' % response.service_id)


    .. versionadded:: 1.4.0

    .. versionchanged:: 1.5.0
@@ -2971,6 +2974,9 @@ class Controller(BaseController):
    .. versionchanged:: 1.7.0
       Added the timeout and max_streams arguments.

    .. versionchanged:: 2.0.0
       Added the client_auth_v3 argument.

    :param ports: hidden service port(s) or mapping of hidden
      service ports to their targets
    :param key_type: type of key being provided, generates a new key if
@@ -2984,9 +2990,11 @@ class Controller(BaseController):
    :param await_publication: blocks until our descriptor is successfully
      published if **True**
    :param timeout: seconds to wait when **await_result** is **True**
    :param basic_auth: required user credentials to access this service
    :param basic_auth: required user credentials to access a v2 service
    :param max_streams: maximum number of streams the hidden service will
      accept, unlimited if zero or not set
    :param str client_auth_v3: base32-encoded public key for **version 3**
      onion services that require client authentication

    :returns: :class:`~stem.response.add_onion.AddOnionResponse` with the response

@@ -3024,6 +3032,12 @@ class Controller(BaseController):
    if (await self.get_conf('HiddenServiceSingleHopMode', None)) == '1' and (await self.get_conf('HiddenServiceNonAnonymousMode', None)) == '1':
      flags.append('NonAnonymous')

    if client_auth_v3 is not None:
      if await self.get_version() < stem.version.Requirement.ONION_SERVICE_AUTH_ADD:
        raise stem.UnsatisfiableRequest(message = 'Client authentication support for v3 onions was added to ADD_ONION in tor version %s' % stem.version.Requirement.ONION_SERVICE_AUTH_ADD)

      flags.append('V3Auth')

    if flags:
      request += ' Flags=%s' % ','.join(flags)

@@ -3048,6 +3062,9 @@ class Controller(BaseController):
        else:
          request += ' ClientAuth=%s' % client_name

    if client_auth_v3 is not None:
      request += ' ClientAuthV3=%s' % client_auth_v3

    response = stem.response._convert_to_add_onion(stem.response._convert_to_add_onion(await self.msg(request)))

    if await_publication:
+10 −8
Original line number Diff line number Diff line
@@ -26,14 +26,15 @@ easily parsed and compared, for instance...

  Enumerations for the version requirements of features.

  =========================== ===========
  ========================== ===========
  Requirement                Description
  =========================== ===========
  ========================== ===========
  **DORMANT_MODE**           **DORMANT** and **ACTIVE** :data:`~stem.Signal`
  **DROPTIMEOUTS**           **DROPTIMEOUTS** controller command
  **HSFETCH_V3**             HSFETCH for version 3 hidden services
  **ONION_CLIENT_AUTH_ADD**  **ONION_CLIENT_AUTH_ADD** controller command
  =========================== ===========
  **ONION_SERVICE_AUTH_ADD** For adding ClientAuthV3 to a v3 onion service via ADD_ONION
  ========================== ===========
"""

import functools
@@ -223,4 +224,5 @@ Requirement = stem.util.enum.Enum(
  ('DROPTIMEOUTS', Version('0.4.5.0-alpha')),
  ('HSFETCH_V3', Version('0.4.1.1-alpha')),
  ('ONION_CLIENT_AUTH_ADD', Version('0.4.3.1-alpha')),
  ('ONION_SERVICE_AUTH_ADD', Version('0.4.6.1-alpha')),
)
+42 −1
Original line number Diff line number Diff line
@@ -475,6 +475,7 @@ class TestController(unittest.TestCase):
      self.assertFalse(await controller.is_set('ConnLimit'))

  @test.require.controller
  @test.require.version_older_than(stem.version.Version('0.4.6.1-alpha'))
  @async_test
  async def test_hidden_services_conf(self):
    """
@@ -593,7 +594,7 @@ class TestController(unittest.TestCase):
    async with await test.runner.get_runner().get_tor_controller() as controller:
      self.assertEqual([], await controller.list_ephemeral_hidden_services())
      self.assertEqual([], await controller.list_ephemeral_hidden_services(detached = True))
      self.assertEqual(False, await controller.remove_ephemeral_hidden_service('gfzprpioee3hoppz'))
      self.assertEqual(False, await controller.remove_ephemeral_hidden_service(SERVICE_ID))

  @test.require.controller
  @async_test
@@ -604,6 +605,7 @@ class TestController(unittest.TestCase):
          await controller.create_ephemeral_hidden_service(ports)

  @test.require.controller
  @test.require.version_older_than(stem.version.Version('0.4.6.1-alpha'))
  @async_test
  async def test_ephemeral_hidden_services_v2(self):
    """
@@ -694,6 +696,7 @@ class TestController(unittest.TestCase):
        self.assertEqual(0, len(await second_controller.list_ephemeral_hidden_services()))

  @test.require.controller
  @test.require.version_older_than(stem.version.Version('0.4.6.1-alpha'))
  @async_test
  async def test_with_ephemeral_hidden_services_basic_auth(self):
    """
@@ -714,6 +717,44 @@ class TestController(unittest.TestCase):
      self.assertEqual([], await controller.list_ephemeral_hidden_services())

  @test.require.controller
  @test.require.version(stem.version.Requirement.ONION_SERVICE_AUTH_ADD)
  @async_test
  async def test_with_ephemeral_hidden_services_v3_client_auth(self):
    """
    Exercises creating v3 ephemeral hidden services with ClientAuthV3.
    """

    runner = test.runner.get_runner()

    async with await runner.get_tor_controller() as controller:
      response = await controller.create_ephemeral_hidden_service(4567, key_content = 'ED25519-V3', client_auth_v3='FGTORMIDKR7T2PR632HSHLWA4G6HF5TCWSGMHDUU4LWBEFTAVYQQ')
      self.assertEqual([response.service_id], await controller.list_ephemeral_hidden_services())
      self.assertTrue(response.private_key is not None)
      self.assertEqual('ED25519-V3', response.private_key_type)
      self.assertEqual({}, response.client_auth)

      # drop the service

      self.assertEqual(True, await controller.remove_ephemeral_hidden_service(response.service_id))
      self.assertEqual([], await controller.list_ephemeral_hidden_services())

  @test.require.controller
  @test.require.version(stem.version.Requirement.ONION_SERVICE_AUTH_ADD)
  @async_test
  async def test_with_ephemeral_hidden_services_v3_client_auth_invalid(self):
    """
    Exercises creating v3 ephemeral hidden services with ClientAuthV3 but
    with an invalid public key.
    """

    runner = test.runner.get_runner()

    async with await runner.get_tor_controller() as controller:
      with self.assertRaisesWith(stem.ProtocolError, "ADD_ONION response didn't have an OK status: Cannot decode v3 client auth key"):
        await controller.create_ephemeral_hidden_service(4567, key_content = 'ED25519-V3', client_auth_v3='badkey')

  @test.require.controller
  @test.require.version_older_than(stem.version.Version('0.4.6.1-alpha'))
  @async_test
  async def test_with_ephemeral_hidden_services_basic_auth_no_credentials(self):
    """
+10 −0
Original line number Diff line number Diff line
@@ -125,6 +125,16 @@ def version(req_version):
  return needs(lambda: test.tor_version() >= req_version, 'requires %s' % req_version)


def version_older_than(req_version):
  """
  Skips the test unless we meet a version older than the requested version.

  :param stem.version.Version req_version: the version that tor should be older than
  """

  return needs(lambda: test.tor_version() < req_version, 'requires %s' % req_version)


cryptography = needs(lambda: CRYPTOGRAPHY_AVAILABLE, 'requires cryptography')
proc = needs(stem.util.proc.is_available, 'proc unavailable')
controller = needs(_can_access_controller, 'no connection')