[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Parse descriptor outer layer
commit 01b81dca033dbcaa75ed13e85acd57864dd5f9fb
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Thu Oct 3 16:01:54 2019 -0700
Parse descriptor outer layer
Quick and dirty parser for the outer layer of hidden service descriptors.
---
stem/descriptor/hidden_service.py | 82 +++++++++++++++++++++++++++++--
stem/descriptor/hsv3_crypto.py | 31 ++----------
test/unit/descriptor/hidden_service_v3.py | 20 ++++++++
3 files changed, 104 insertions(+), 29 deletions(-)
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index e162079b..a20d853a 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -47,6 +47,7 @@ from stem.descriptor import (
_read_until_keywords,
_bytes_for_block,
_value,
+ _values,
_parse_simple_line,
_parse_int_line,
_parse_timestamp_line,
@@ -103,6 +104,12 @@ STEALTH_AUTH = 2
CHECKSUM_CONSTANT = b'.onion checksum'
+class DecryptionFailure(Exception):
+ """
+ Failure to decrypt the hidden service descriptor's introduction-points.
+ """
+
+
class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTION_POINTS_ATTR.keys())):
"""
:var str identifier: hash of this introduction point's identity key
@@ -115,9 +122,15 @@ class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTI
"""
-class DecryptionFailure(Exception):
+class AuthorizedClient(collections.namedtuple('AuthorizedClient', ['id', 'iv', 'cookie'])):
"""
- Failure to decrypt the hidden service descriptor's introduction-points.
+ Client authorized to use a v3 hidden service.
+
+ .. versionadded:: 1.8.0
+
+ :var str id: base64 encoded client id
+ :var str iv: base64 encoded randomized initialization vector
+ :var str cookie: base64 encoded authentication cookie
"""
@@ -191,6 +204,22 @@ def _parse_introduction_points_line(descriptor, entries):
raise ValueError("'introduction-points' isn't base64 encoded content:\n%s" % block_contents)
+def _parse_v3_outer_clients(descriptor, entries):
+ # "auth-client" client-id iv encrypted-cookie
+
+ clients = {}
+
+ for value in _values('auth-client', entries):
+ value_comp = value.split()
+
+ if len(value_comp) < 3:
+ raise ValueError('auth-client should have a client-id, iv, and cookie: auth-client %s' % value)
+
+ clients[value_comp[0]] = AuthorizedClient(value_comp[0], value_comp[1], value_comp[2])
+
+ descriptor.clients = clients
+
+
_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')
@@ -205,6 +234,10 @@ _parse_revision_counter_line = _parse_int_line('revision-counter', 'revision_cou
_parse_superencrypted_line = _parse_key_block('superencrypted', 'superencrypted', 'MESSAGE')
_parse_v3_signature_line = _parse_simple_line('signature', 'signature')
+_parse_v3_outer_auth_type = _parse_simple_line('desc-auth-type', 'auth_type')
+_parse_v3_outer_ephemeral_key = _parse_simple_line('desc-auth-ephemeral-key', 'ephemeral_key')
+_parse_v3_outer_encrypted = _parse_key_block('encrypted', 'encrypted', 'MESSAGE')
+
class BaseHiddenServiceDescriptor(Descriptor):
"""
@@ -579,7 +612,7 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
if outer_layer:
return outter_layer_plaintext
- inner_layer_ciphertext = stem.descriptor.hsv3_crypto.parse_superencrypted_plaintext(outter_layer_plaintext)
+ inner_layer_ciphertext = OuterLayer(outter_layer_plaintext).encrypted
inner_layer_plaintext = stem.descriptor.hsv3_crypto.decrypt_inner_layer(inner_layer_ciphertext, self.revision_counter, identity_public_key, blinded_key, subcredential)
@@ -615,6 +648,49 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
return pubkey
+class OuterLayer(Descriptor):
+ """
+ Initial encryped layer of a hidden service v3 descriptor (`spec
+ <https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n1154>`_).
+
+ .. versionadded:: 1.8.0
+
+ :var str auth_type: **\\*** encryption scheme used for descriptor authorization
+ :var str ephemeral_key: **\\*** base64 encoded x25519 public key
+ :var dict clients: **\\*** mapping of authorized client ids to their
+ :class:`~stem.descriptor.hidden_service.AuthorizedClient`
+ :var str encrypted: **\\*** encrypted descriptor inner layer
+
+ **\\*** attribute is either required when we're parsed with validation or has
+ a default value, others are left as **None** if undefined
+ """
+
+ ATTRIBUTES = {
+ 'auth_type': (None, _parse_v3_outer_auth_type),
+ 'ephemeral_key': (None, _parse_v3_outer_ephemeral_key),
+ 'clients': ({}, _parse_v3_outer_clients),
+ 'encrypted': (None, _parse_v3_outer_encrypted),
+ }
+
+ PARSER_FOR_LINE = {
+ 'desc-auth-type': _parse_v3_outer_auth_type,
+ 'desc-auth-ephemeral-key': _parse_v3_outer_ephemeral_key,
+ 'auth-client': _parse_v3_outer_clients,
+ 'encrypted': _parse_v3_outer_encrypted,
+ }
+
+ def __init__(self, content, validate = False):
+ content = content.rstrip(b'\x00') # strip null byte padding
+
+ super(OuterLayer, self).__init__(content, lazy_load = not validate)
+ entries = _descriptor_components(content, validate)
+
+ if validate:
+ self._parse(entries, validate)
+ else:
+ self._entries = entries
+
+
# TODO: drop this alias in stem 2.x
HiddenServiceDescriptor = HiddenServiceDescriptorV2
diff --git a/stem/descriptor/hsv3_crypto.py b/stem/descriptor/hsv3_crypto.py
index 2f9f2d66..078d71b4 100644
--- a/stem/descriptor/hsv3_crypto.py
+++ b/stem/descriptor/hsv3_crypto.py
@@ -84,6 +84,9 @@ def _decrypt_descriptor_layer(ciphertext_blob_b64, revision_counter, public_iden
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
+ if ciphertext_blob_b64.startswith('-----BEGIN MESSAGE-----\n') and ciphertext_blob_b64.endswith('\n-----END MESSAGE-----'):
+ ciphertext_blob_b64 = ciphertext_blob_b64[24:-22]
+
# decode the thing
ciphertext_blob = base64.b64decode(ciphertext_blob_b64)
@@ -119,32 +122,8 @@ def _decrypt_descriptor_layer(ciphertext_blob_b64, revision_counter, public_iden
def decrypt_outter_layer(superencrypted_blob_b64, revision_counter, public_identity_key, blinded_key, subcredential):
- secret_data = blinded_key
- string_constant = b'hsdir-superencrypted-data'
-
- # XXX Remove the BEGIN MESSSAGE around the thing
- superencrypted_blob_b64_lines = superencrypted_blob_b64.split('\n')
- superencrypted_blob_b64 = ''.join(superencrypted_blob_b64_lines[1:-1])
-
- return _decrypt_descriptor_layer(superencrypted_blob_b64, revision_counter, public_identity_key, subcredential, secret_data, string_constant)
+ return _decrypt_descriptor_layer(superencrypted_blob_b64, revision_counter, public_identity_key, subcredential, blinded_key, b'hsdir-superencrypted-data')
def decrypt_inner_layer(encrypted_blob_b64, revision_counter, public_identity_key, blinded_key, subcredential):
- secret_data = blinded_key
- string_constant = b'hsdir-encrypted-data'
-
- return _decrypt_descriptor_layer(encrypted_blob_b64, revision_counter, public_identity_key, subcredential, secret_data, string_constant)
-
-
-def parse_superencrypted_plaintext(outter_layer_plaintext):
- """Super hacky function to parse the superencrypted plaintext. This will need to be replaced by proper stem code."""
-
- START_CONSTANT = b'-----BEGIN MESSAGE-----\n'
- END_CONSTANT = b'\n-----END MESSAGE-----'
-
- start = outter_layer_plaintext.find(START_CONSTANT)
- end = outter_layer_plaintext.find(END_CONSTANT)
-
- start = start + len(START_CONSTANT)
-
- return outter_layer_plaintext[start:end]
+ return _decrypt_descriptor_layer(encrypted_blob_b64, revision_counter, public_identity_key, subcredential, blinded_key, b'hsdir-encrypted-data')
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index 3824c8a6..3140d193 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -11,6 +11,7 @@ import stem.prereq
from stem.descriptor.hidden_service import (
REQUIRED_V3_FIELDS,
HiddenServiceDescriptorV3,
+ OuterLayer,
)
from test.unit.descriptor import (
@@ -58,6 +59,25 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
with open(get_resource('hidden_service_v3_inner_layer'), 'rb') as outer_layer_file:
self.assertEqual(outer_layer_file.read(), desc._decrypt(HS_ADDRESS, outer_layer = False))
+ def test_outer_layer(self):
+ """
+ Parse the outer layer of our test descriptor.
+ """
+
+ with open(get_resource('hidden_service_v3_outer_layer'), 'rb') as descriptor_file:
+ desc = OuterLayer(descriptor_file.read())
+
+ self.assertEqual('x25519', desc.auth_type)
+ self.assertEqual('WjZCU9sV1oxkxaPcd7/YozeZgq0lEs6DhWyrdYRNJR4=', desc.ephemeral_key)
+ self.assertTrue('BsRYMH/No+LgetIFv' in desc.encrypted)
+
+ client = desc.clients['D0Bz0OlEMCg']
+
+ self.assertEqual(16, len(desc.clients))
+ self.assertEqual('D0Bz0OlEMCg', client.id)
+ self.assertEqual('or3nS3ScSPYfLJuP9osGiQ', client.iv)
+ self.assertEqual('B40RdIWhw7kdA7lt3KJPvQ', client.cookie)
+
def test_required_fields(self):
"""
Check that we require the mandatory fields.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits