commit deba20b19b742ae51f60fbff1432823abe15d4af Author: Damian Johnson atagar@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):
tor-commits@lists.torproject.org