[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