[tor-commits] [stem/master] Move intro point parsing into class

atagar at torproject.org atagar at torproject.org
Sun Nov 17 23:40:39 UTC 2019


commit deba20b19b742ae51f60fbff1432823abe15d4af
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri Oct 25 17:24:17 2019 -0700

    Move intro point parsing into class
    
    IntroductionPointV3 requires an encode() method, so fitting for the class to
    first include its parser.
---
 stem/descriptor/hidden_service.py                  | 122 ++++++++++-----------
 .../descriptor/data/hidden_service_v3_intro_point  |  28 +++++
 test/unit/descriptor/hidden_service_v3.py          |  18 +++
 3 files changed, 106 insertions(+), 62 deletions(-)

diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 1819dfc5..72fc9b2b 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -163,9 +163,47 @@ class IntroductionPointV3(collections.namedtuple('IntroductionPointV3', ['link_s
   :var str enc_key_raw: base64 introduction request encryption key
   :var stem.certificate.Ed25519Certificate enc_key_cert: cross-certifier of the signing key by the encryption key
   :var str legacy_key_raw: base64 legacy introduction point RSA public key
-  :var stem.certificate.Ed25519Certificate legacy_key_cert: cross-certifier of the signing key by the legacy key
+  :var str legacy_key_cert: cross-certifier of the signing key by the legacy key
   """
 
+  @staticmethod
+  def parse(content):
+    """
+    Parses an introduction point from its descriptor content.
+
+    :param str content: descriptor content to parse
+
+    :returns: :class:`~stem.descriptor.hidden_service.IntroductionPointV3` for the descriptor content
+
+    :raises: **ValueError** if descriptor content is malformed
+    """
+
+    entry = _descriptor_components(content, False)
+    link_specifiers = IntroductionPointV3._parse_link_specifiers(_value('introduction-point', entry))
+
+    onion_key_line = _value('onion-key', entry)
+    onion_key = onion_key_line[5:] if onion_key_line.startswith('ntor ') else None
+
+    _, block_type, auth_key_cert = entry['auth-key'][0]
+    auth_key_cert = Ed25519Certificate.from_base64(auth_key_cert)
+
+    if block_type != 'ED25519 CERT':
+      raise ValueError('Expected auth-key to have an ed25519 certificate, but was %s' % block_type)
+
+    enc_key_line = _value('enc-key', entry)
+    enc_key = enc_key_line[5:] if enc_key_line.startswith('ntor ') else None
+
+    _, block_type, enc_key_cert = entry['enc-key-cert'][0]
+    enc_key_cert = Ed25519Certificate.from_base64(enc_key_cert)
+
+    if block_type != 'ED25519 CERT':
+      raise ValueError('Expected enc-key-cert to have an ed25519 certificate, but was %s' % block_type)
+
+    legacy_key = entry['legacy-key'][0][2] if 'legacy-key' in entry else None
+    legacy_key_cert = entry['legacy-key-cert'][0][2] if 'legacy-key-cert' in entry else None
+
+    return IntroductionPointV3(link_specifiers, onion_key, auth_key_cert, enc_key, enc_key_cert, legacy_key, legacy_key_cert)
+
   def onion_key(self):
     """
     Provides our ntor introduction point public key.
@@ -221,6 +259,25 @@ class IntroductionPointV3(collections.namedtuple('IntroductionPointV3', ['link_s
 
     return X25519PublicKey.from_public_bytes(base64.b64decode(value))
 
+  @staticmethod
+  def _parse_link_specifiers(content):
+    try:
+      content = base64.b64decode(content)
+    except Exception as exc:
+      raise ValueError('Unable to base64 decode introduction point (%s): %s' % (exc, content))
+
+    link_specifiers = []
+    count, content = stem.client.datatype.Size.CHAR.pop(content)
+
+    for i in range(count):
+      link_specifier, content = stem.client.datatype.LinkSpecifier.pop(content)
+      link_specifiers.append(link_specifier)
+
+    if content:
+      raise ValueError('Introduction point had excessive data (%s)' % content)
+
+    return link_specifiers
+
 
 class AlternateIntroductionPointV3(object):
   """
@@ -493,73 +550,14 @@ def _parse_v3_introduction_points(descriptor, entries):
 
     while remaining:
       div = remaining.find('\nintroduction-point ', 10)
+      content, remaining = (remaining[:div], remaining[div + 1:]) if div != -1 else (remaining, '')
 
-      if div == -1:
-        intro_point_str = remaining
-        remaining = ''
-      else:
-        intro_point_str = remaining[:div]
-        remaining = remaining[div + 1:]
-
-      entry = _descriptor_components(intro_point_str, False)
-      link_specifiers = _parse_link_specifiers(_value('introduction-point', entry))
-
-      onion_key_line = _value('onion-key', entry)
-      onion_key = onion_key_line[5:] if onion_key_line.startswith('ntor ') else None
-
-      _, block_type, auth_key_cert = entry['auth-key'][0]
-      auth_key_cert = Ed25519Certificate.from_base64(auth_key_cert)
-
-      if block_type != 'ED25519 CERT':
-        raise ValueError('Expected auth-key to have an ed25519 certificate, but was %s' % block_type)
-
-      enc_key_line = _value('enc-key', entry)
-      enc_key = enc_key_line[5:] if enc_key_line.startswith('ntor ') else None
-
-      _, block_type, enc_key_cert = entry['enc-key-cert'][0]
-      enc_key_cert = Ed25519Certificate.from_base64(enc_key_cert)
-
-      if block_type != 'ED25519 CERT':
-        raise ValueError('Expected enc-key-cert to have an ed25519 certificate, but was %s' % block_type)
-
-      legacy_key = entry['legacy-key'][0][2] if 'legacy-key' in entry else None
-      legacy_key_cert = entry['legacy-key-cert'][0][2] if 'legacy-key-cert' in entry else None
-
-      introduction_points.append(
-        IntroductionPointV3(
-          link_specifiers,
-          onion_key,
-          auth_key_cert,
-          enc_key,
-          enc_key_cert,
-          legacy_key,
-          legacy_key_cert,
-        )
-      )
+      introduction_points.append(IntroductionPointV3.parse(content))
 
     descriptor.introduction_points = introduction_points
     del descriptor._unparsed_introduction_points
 
 
-def _parse_link_specifiers(val):
-  try:
-    val = base64.b64decode(val)
-  except Exception as exc:
-    raise ValueError('Unable to base64 decode introduction point (%s): %s' % (exc, val))
-
-  link_specifiers = []
-  count, val = stem.client.datatype.Size.CHAR.pop(val)
-
-  for i in range(count):
-    link_specifier, val = stem.client.datatype.LinkSpecifier.pop(val)
-    link_specifiers.append(link_specifier)
-
-  if val:
-    raise ValueError('Introduction point had excessive data (%s)' % val)
-
-  return link_specifiers
-
-
 _parse_v2_version_line = _parse_int_line('version', 'version', allow_negative = False)
 _parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id')
 _parse_permanent_key_line = _parse_key_block('permanent-key', 'permanent_key', 'RSA PUBLIC KEY')
diff --git a/test/unit/descriptor/data/hidden_service_v3_intro_point b/test/unit/descriptor/data/hidden_service_v3_intro_point
new file mode 100644
index 00000000..618d0e80
--- /dev/null
+++ b/test/unit/descriptor/data/hidden_service_v3_intro_point
@@ -0,0 +1,28 @@
+introduction-point AgIUQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0MABgUGBwgjKQ==
+onion-key ntor AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+auth-key
+-----BEGIN ED25519 CERT-----
+AQkABl60Acq8QW8O7ONgImfilmjrEIeISkZmGuedsdkZucakUBZbAQAgBACS5oD6
+V1UufUhMnSo+20b7wHblTqkLd7uE4+bVZX1Soded2A7SaJOyvI2FBNvljCNgl5T/
+eLNpci4yTizyDv2A0/QB4SyaZ2+SOM/uQn3DKKyhUwwNuaD/sSuUI25gkgY=
+-----END ED25519 CERT-----
+enc-key ntor AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+enc-key-cert
+-----BEGIN ED25519 CERT-----
+AQsABqUQARe6uX12UazJAo5Qt2iP0rJ29hq/GEEi28dAsKqCOHa6AQAgBACS5oD6
+V1UufUhMnSo+20b7wHblTqkLd7uE4+bVZX1SoY1XfpJjLTI3tJwIrFM/JFP3XbVF
+CtwFlIHgSS1/M9Rr+eznM17+5hd+0SHL4/+WV5ukxyPOWIL6X1z+KPg4hA0=
+-----END ED25519 CERT-----
+legacy-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAMO3ZXrcA+PclKppGCh9TOG0H6mubTAgji4fLF87GelggQs5bnPdQeaS
+v4HgP42J/mMinSLpbg5LhL5gd7AqwOxe9cpEhbvwrM63ot7gkj2tJqs2PLlokqSx
+ZBEAssKbE/8F2iVoEWoXd8g8Pn5nG7wRKDGGQRAjintrBSncTvfRAgMBAAE=
+-----END RSA PUBLIC KEY-----
+legacy-key-cert
+-----BEGIN CROSSCERT-----
+kuaA+ldVLn1ITJ0qPttG+8B25U6pC3e7hOPm1WV9UqEABl60gH1LLE5C2kl5BBpb
+E2Ajh6kJuf2fXMW7csYYNtPACZjFoG+kb16fh7y9L2pLuBFNKpkVDMsiQVcdwWWg
+Nu6qpGj1vHDR1XUM7ocoXB3QMVXCIxvA9b8k3q7KFvXgImi9GZ7l1/K+emm58MYM
+CxhNKazjiFgXjbs9kf+S9HxaF/Yw
+-----END CROSSCERT-----
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index b57a3ebf..6f8ba69e 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -17,6 +17,7 @@ from stem.descriptor.hidden_service import (
   CHECKSUM_CONSTANT,
   REQUIRED_V3_FIELDS,
   X25519_AVAILABLE,
+  IntroductionPointV3,
   AlternateIntroductionPointV3,
   HiddenServiceDescriptorV3,
   OuterLayer,
@@ -60,6 +61,9 @@ with open(get_resource('hidden_service_v3_outer_layer')) as outer_layer_file:
 with open(get_resource('hidden_service_v3_inner_layer')) as inner_layer_file:
   INNER_LAYER_STR = inner_layer_file.read()
 
+with open(get_resource('hidden_service_v3_intro_point')) as intro_point_file:
+  INTRO_POINT_STR = intro_point_file.read()
+
 
 def _pubkeys_are_equal(pubkey1, pubkey2):
   """
@@ -266,6 +270,20 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
     self.assertRaisesWith(ValueError, "'boom.onion' isn't a valid hidden service v3 address", HiddenServiceDescriptorV3._public_key_from_address, 'boom')
     self.assertRaisesWith(ValueError, 'Bad checksum (expected def7 but was 842e)', HiddenServiceDescriptorV3._public_key_from_address, '5' * 56)
 
+  def test_intro_point_parse(self):
+    """
+    Parse a v3 introduction point.
+    """
+
+    intro_point = IntroductionPointV3.parse(INTRO_POINT_STR)
+
+    self.assertEqual('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', intro_point.onion_key_raw)
+    self.assertTrue('0Acq8QW8O7O' in intro_point.auth_key_cert.to_base64())
+    self.assertEqual('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', intro_point.enc_key_raw)
+    self.assertTrue('4807i5', intro_point.enc_key_cert.to_base64())
+    self.assertTrue('JAoGBAMO3' in intro_point.legacy_key_raw)
+    self.assertTrue('Ln1ITJ0qP' in intro_point.legacy_key_cert)
+
   @require_x25519
   @test.require.ed25519_support
   def test_intro_point_crypto(self):





More information about the tor-commits mailing list