[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Merge hsv3_crypto into hidden_service.py
commit 80def07ebfdbb2fe00b85a6721fd3cbb2b4220b2
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sat Oct 5 18:41:45 2019 -0700
Merge hsv3_crypto into hidden_service.py
---
stem/client/datatype.py | 4 +-
stem/descriptor/hidden_service.py | 88 +++++++++++++++-----
stem/descriptor/hsv3_crypto.py | 128 ------------------------------
test/unit/descriptor/hidden_service_v3.py | 8 +-
4 files changed, 76 insertions(+), 152 deletions(-)
diff --git a/stem/client/datatype.py b/stem/client/datatype.py
index 1f49388b..7e33e353 100644
--- a/stem/client/datatype.py
+++ b/stem/client/datatype.py
@@ -628,7 +628,7 @@ class LinkByFingerprint(LinkSpecifier):
if len(value) != 20:
raise ValueError('Fingerprint link specifiers should be twenty bytes, but was %i instead: %s' % (len(value), binascii.hexlify(value)))
- self.fingerprint = value
+ self.fingerprint = stem.util.str_tools._to_unicode(value)
class LinkByEd25519(LinkSpecifier):
@@ -644,7 +644,7 @@ class LinkByEd25519(LinkSpecifier):
if len(value) != 32:
raise ValueError('Fingerprint link specifiers should be thirty two bytes, but was %i instead: %s' % (len(value), binascii.hexlify(value)))
- self.fingerprint = value
+ self.fingerprint = stem.util.str_tools._to_unicode(value)
class KDF(collections.namedtuple('KDF', ['key_hash', 'forward_digest', 'backward_digest', 'forward_key', 'backward_key'])):
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index e4ee13e3..601348d9 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -30,9 +30,9 @@ import binascii
import collections
import hashlib
import io
+import struct
import stem.client.datatype
-import stem.descriptor.hsv3_crypto
import stem.prereq
import stem.util.connection
import stem.util.str_tools
@@ -105,6 +105,12 @@ BASIC_AUTH = 1
STEALTH_AUTH = 2
CHECKSUM_CONSTANT = b'.onion checksum'
+SALT_LEN = 16
+MAC_LEN = 32
+
+S_KEY_LEN = 32
+S_IV_LEN = 16
+
class DecryptionFailure(Exception):
"""
@@ -132,6 +138,8 @@ class IntroductionPointV3(collections.namedtuple('IntroductionPointV3', ['link_s
"""
Introduction point for a v3 hidden service.
+ .. versionadded:: 1.8.0
+
:var list link_specifiers: :class:`~stem.client.datatype.LinkSpecifier` where this service is reachable
:var str onion_key: ntor introduction point public key
:var str auth_key: cross-certifier of the signing key
@@ -194,6 +202,46 @@ def _parse_file(descriptor_file, desc_type = None, validate = False, **kwargs):
break # done parsing file
+def _decrypt_layer(encrypted_block, constant, revision_counter, subcredential, blinded_key):
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.backends import default_backend
+
+ def pack(val):
+ return struct.pack('>Q', val)
+
+ if encrypted_block.startswith('-----BEGIN MESSAGE-----\n') and encrypted_block.endswith('\n-----END MESSAGE-----'):
+ encrypted_block = encrypted_block[24:-22]
+
+ try:
+ encrypted = base64.b64decode(encrypted_block)
+ except:
+ raise ValueError('Unable to decode encrypted block as base64')
+
+ if len(encrypted) < SALT_LEN + MAC_LEN:
+ raise ValueError('Encrypted block malformed (only %i bytes)' % len(encrypted))
+
+ salt = encrypted[:SALT_LEN]
+ ciphertext = encrypted[SALT_LEN:-MAC_LEN]
+ expected_mac = encrypted[-MAC_LEN:]
+
+ kdf = hashlib.shake_256(blinded_key + subcredential + pack(revision_counter) + salt + constant)
+ keys = kdf.digest(S_KEY_LEN + S_IV_LEN + MAC_LEN)
+
+ secret_key = keys[:S_KEY_LEN]
+ secret_iv = keys[S_KEY_LEN:S_KEY_LEN + S_IV_LEN]
+ mac_key = keys[S_KEY_LEN + S_IV_LEN:]
+
+ mac = hashlib.sha3_256(pack(len(mac_key)) + mac_key + pack(len(salt)) + salt + ciphertext).digest()
+
+ if mac != expected_mac:
+ raise ValueError('Malformed mac (expected %s, but was %s)' % (expected_mac, mac))
+
+ cipher = Cipher(algorithms.AES(secret_key), modes.CTR(secret_iv), default_backend())
+ decryptor = cipher.decryptor()
+
+ return stem.util.str_tools._to_unicode(decryptor.update(ciphertext) + decryptor.finalize())
+
+
def _parse_protocol_versions_line(descriptor, entries):
value = _value('protocol-versions', entries)
@@ -693,14 +741,13 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
else:
self._entries = entries
- def decrypt(self, onion_address, validate = False):
+ def decrypt(self, onion_address):
"""
Decrypt this descriptor. Hidden serice descriptors contain two encryption
layers (:class:`~stem.descriptor.hidden_service.OuterLayer` and
:class:`~stem.descriptor.hidden_service.InnerLayer`).
:param str onion_address: hidden service address this descriptor is from
- :param bool validate: perform validation checks on decrypted content
:returns: :class:`~stem.descriptor.hidden_service.InnerLayer` with our
decrypted content
@@ -721,19 +768,15 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
if not blinded_key:
raise ValueError('No signing key is present')
- identity_public_key = HiddenServiceDescriptorV3._public_key_from_address(onion_address)
-
# credential = H('credential' | public-identity-key)
# subcredential = H('subcredential' | credential | blinded-public-key)
+ identity_public_key = HiddenServiceDescriptorV3._public_key_from_address(onion_address)
credential = hashlib.sha3_256(b'credential%s' % (identity_public_key)).digest()
subcredential = hashlib.sha3_256(b'subcredential%s%s' % (credential, blinded_key)).digest()
- outer_layer = OuterLayer(stem.descriptor.hsv3_crypto.decrypt_outter_layer(self.superencrypted, self.revision_counter, blinded_key, subcredential), validate)
-
- inner_layer_plaintext = stem.descriptor.hsv3_crypto.decrypt_inner_layer(outer_layer.encrypted, self.revision_counter, blinded_key, subcredential)
-
- self._inner_layer = InnerLayer(inner_layer_plaintext, validate, outer_layer)
+ outer_layer = OuterLayer._decrypt(self.superencrypted, self.revision_counter, subcredential, blinded_key)
+ self._inner_layer = InnerLayer._decrypt(outer_layer, self.revision_counter, subcredential, blinded_key)
return self._inner_layer
@@ -753,16 +796,16 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
decoded_address = base64.b32decode(onion_address.upper())
pubkey = decoded_address[:32]
- checksum = decoded_address[32:34]
+ expected_checksum = decoded_address[32:34]
version = decoded_address[34:35]
- # validate our address checksum
+ checksum = hashlib.sha3_256(CHECKSUM_CONSTANT + pubkey + version).digest()[:2]
- my_checksum_body = b'%s%s%s' % (CHECKSUM_CONSTANT, pubkey, version)
- my_checksum = hashlib.sha3_256(my_checksum_body).digest()[:2]
+ if expected_checksum != checksum:
+ checksum_str = stem.util.str_tools._to_unicode(binascii.hexlify(checksum))
+ expected_checksum_str = stem.util.str_tools._to_unicode(binascii.hexlify(expected_checksum))
- if (checksum != my_checksum):
- raise ValueError('Bad checksum (expected %s but was %s)' % (binascii.hexlify(checksum), binascii.hexlify(my_checksum)))
+ raise ValueError('Bad checksum (expected %s but was %s)' % (expected_checksum_str, checksum_str))
return pubkey
@@ -798,8 +841,13 @@ class OuterLayer(Descriptor):
'encrypted': _parse_v3_outer_encrypted,
}
+ @staticmethod
+ def _decrypt(encrypted, revision_counter, subcredential, blinded_key):
+ plaintext = _decrypt_layer(encrypted, b'hsdir-superencrypted-data', revision_counter, subcredential, blinded_key)
+ return OuterLayer(plaintext)
+
def __init__(self, content, validate = False):
- content = content.rstrip(b'\x00') # strip null byte padding
+ content = content.rstrip('\x00') # strip null byte padding
super(OuterLayer, self).__init__(content, lazy_load = not validate)
entries = _descriptor_components(content, validate)
@@ -841,9 +889,13 @@ class InnerLayer(Descriptor):
'single-onion-service': _parse_v3_inner_single_service,
}
+ @staticmethod
+ def _decrypt(outer_layer, revision_counter, subcredential, blinded_key):
+ plaintext = _decrypt_layer(outer_layer.encrypted, b'hsdir-encrypted-data', revision_counter, subcredential, blinded_key)
+ return InnerLayer(plaintext, outer_layer = outer_layer)
+
def __init__(self, content, validate = False, outer_layer = None):
super(InnerLayer, self).__init__(content, lazy_load = not validate)
-
self.outer = outer_layer
# inner layer begins with a few header fields, followed by multiple any
diff --git a/stem/descriptor/hsv3_crypto.py b/stem/descriptor/hsv3_crypto.py
deleted file mode 100644
index 9acb5242..00000000
--- a/stem/descriptor/hsv3_crypto.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import base64
-import hashlib
-import struct
-
-"""
-Onion addresses
-
- onion_address = base32(PUBKEY | CHECKSUM | VERSION) + '.onion'
- CHECKSUM = H('.onion checksum' | PUBKEY | VERSION)[:2]
-
- - PUBKEY is the 32 bytes ed25519 master pubkey of the hidden service.
- - VERSION is an one byte version field (default value '\x03')
- - '.onion checksum' is a constant string
- - CHECKSUM is truncated to two bytes before inserting it in onion_address
-"""
-
-
-"""
-Blinded key stuff
-
- Now wrt SRVs, if a client is in the time segment between a new time period
- and a new SRV (i.e. the segments drawn with '-') it uses the current SRV,
- else if the client is in a time segment between a new SRV and a new time
- period (i.e. the segments drawn with '='), it uses the previous SRV.
-"""
-
-pass
-
-"""
-Basic descriptor logic:
-
- SALT = 16 bytes from H(random), changes each time we rebuld the
- descriptor even if the content of the descriptor hasn't changed.
- (So that we don't leak whether the intro point list etc. changed)
-
- secret_input = SECRET_DATA | subcredential | INT_8(revision_counter)
-
- keys = KDF(secret_input | salt | STRING_CONSTANT, S_KEY_LEN + S_IV_LEN + MAC_KEY_LEN)
-
- SECRET_KEY = first S_KEY_LEN bytes of keys
- SECRET_IV = next S_IV_LEN bytes of keys
- MAC_KEY = last MAC_KEY_LEN bytes of keys
-
-
-Layer data:
-
- 2.5.1.1. First layer encryption logic
- SECRET_DATA = blinded-public-key
- STRING_CONSTANT = 'hsdir-superencrypted-data'
-
- 2.5.2.1. Second layer encryption keys
- SECRET_DATA = blinded-public-key | descriptor_cookie
- STRING_CONSTANT = 'hsdir-encrypted-data'
-"""
-
-SALT_LEN = 16
-MAC_LEN = 32
-
-S_KEY_LEN = 32
-S_IV_LEN = 16
-MAC_KEY_LEN = 32
-
-
-def _ciphertext_mac_is_valid(key, salt, ciphertext, mac):
- """
- Instantiate MAC(key=k, message=m) with H(k_len | k | m), where k_len is
- htonll(len(k)).
-
- XXX spec: H(mac_key_len | mac_key | salt_len | salt | encrypted)
- """
-
- # Construct our own MAC first
- key_len = struct.pack('>Q', len(key))
- salt_len = struct.pack('>Q', len(salt))
-
- my_mac_body = b'%s%s%s%s%s' % (key_len, key, salt_len, salt, ciphertext)
- my_mac = hashlib.sha3_256(my_mac_body).digest()
-
- # Compare the two MACs
- return my_mac == mac
-
-
-def _decrypt_descriptor_layer(ciphertext_blob_b64, revision_counter, subcredential, secret_data, string_constant):
- 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)
-
- if (len(ciphertext_blob) < SALT_LEN + MAC_LEN):
- raise ValueError('bad encrypted blob')
-
- salt = ciphertext_blob[:16]
- ciphertext = ciphertext_blob[16:-32]
- mac = ciphertext_blob[-32:]
-
- # INT_8(revision_counter)
- rev_counter_int_8 = struct.pack('>Q', revision_counter)
- secret_input = b'%s%s%s' % (secret_data, subcredential, rev_counter_int_8)
-
- kdf = hashlib.shake_256(b'%s%s%s' % (secret_input, salt, string_constant))
- keys = kdf.digest(S_KEY_LEN + S_IV_LEN + MAC_KEY_LEN)
-
- secret_key = keys[:S_KEY_LEN]
- secret_iv = keys[S_KEY_LEN:S_KEY_LEN + S_IV_LEN]
- mac_key = keys[S_KEY_LEN + S_IV_LEN:]
-
- # Now time to decrypt descriptor
- cipher = Cipher(algorithms.AES(secret_key), modes.CTR(secret_iv), default_backend())
- decryptor = cipher.decryptor()
- decrypted = decryptor.update(ciphertext) + decryptor.finalize()
-
- # validate mac (the mac validates the two fields before the mac)
- if not _ciphertext_mac_is_valid(mac_key, salt, ciphertext, mac):
- raise ValueError('Bad MAC!!!')
-
- return decrypted
-
-
-def decrypt_outter_layer(superencrypted_blob_b64, revision_counter, blinded_key, subcredential):
- return _decrypt_descriptor_layer(superencrypted_blob_b64, revision_counter, subcredential, blinded_key, b'hsdir-superencrypted-data')
-
-
-def decrypt_inner_layer(encrypted_blob_b64, revision_counter, blinded_key, subcredential):
- return _decrypt_descriptor_layer(encrypted_blob_b64, revision_counter, 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 093d5cb6..37781b5f 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -35,13 +35,13 @@ BDwQZ8rhp05oCqhhY3oFHqG9KS7HGzv9g2v1/PrVJMbkfpwu1YK4b3zIZAk=
-----END ED25519 CERT-----\
"""
-with open(get_resource('hidden_service_v3'), 'rb') as descriptor_file:
+with open(get_resource('hidden_service_v3')) as descriptor_file:
HS_DESC_STR = descriptor_file.read()
-with open(get_resource('hidden_service_v3_outer_layer'), 'rb') as outer_layer_file:
+with open(get_resource('hidden_service_v3_outer_layer')) as outer_layer_file:
OUTER_LAYER_STR = outer_layer_file.read()
-with open(get_resource('hidden_service_v3_inner_layer'), 'rb') as inner_layer_file:
+with open(get_resource('hidden_service_v3_inner_layer')) as inner_layer_file:
INNER_LAYER_STR = inner_layer_file.read()
@@ -79,7 +79,7 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
inner_layer = desc.decrypt(HS_ADDRESS)
self.assertEqual(INNER_LAYER_STR, str(inner_layer))
- self.assertEqual(OUTER_LAYER_STR.rstrip(b'\x00'), str(inner_layer.outer))
+ self.assertEqual(OUTER_LAYER_STR.rstrip('\x00'), str(inner_layer.outer))
def test_outer_layer(self):
"""
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits