commit 9ffca2043c66544c0d54885ae3d19b10dcc652f1 Author: George Kadianakis desnacked@riseup.net Date: Thu Oct 10 20:56:17 2019 +0300
Move some stuff back to hsv3_crypto.
These functions are gonna be used both by the decoding code and the encoding code.
We are now starting to write an encode-to-decode unittest and when debuggin the descriptor encryption it's very useful to have the encryption-related code centralized in one function. --- stem/descriptor/hidden_service.py | 31 +++++-------- stem/descriptor/hsv3_crypto.py | 98 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 19 deletions(-)
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py index 4a75bc88..305dfaf7 100644 --- a/stem/descriptor/hidden_service.py +++ b/stem/descriptor/hidden_service.py @@ -42,7 +42,9 @@ import stem.util.connection import stem.util.str_tools import stem.util.tor_tools
-from stem.descriptor.certificate import Ed25519Certificate +import stem.descriptor.hsv3_crypto as hsv3_crypto + +from stem.descriptor.certificate import Ed25519Certificate, CertType
from stem.descriptor import ( PGP_BLOCK_END, @@ -263,7 +265,7 @@ class IntroductionPointV3(object): return body
class AuthorizedClient(collections.namedtuple('AuthorizedClient', ['id', 'iv', 'cookie'])): - """ + """ Client authorized to use a v3 hidden service.
.. versionadded:: 1.8.0 @@ -318,9 +320,6 @@ def _decrypt_layer(encrypted_block, constant, revision_counter, subcredential, b 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]
@@ -336,14 +335,10 @@ def _decrypt_layer(encrypted_block, constant, revision_counter, subcredential, b 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, secret_iv, mac_key = hsv3_crypto.get_desc_keys(blinded_key, constant, + subcredential, revision_counter, salt, )
- 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() + mac = hsv3_crypto.get_desc_encryption_mac(mac_key, salt, ciphertext)
if mac != expected_mac: raise ValueError('Malformed mac (expected %s, but was %s)' % (expected_mac, mac)) @@ -844,6 +839,7 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor): super(HiddenServiceDescriptorV3, self).__init__(raw_contents, lazy_load = not validate)
self._inner_layer = None + self._outer_layer = None entries = _descriptor_components(raw_contents, validate)
if validate: @@ -889,15 +885,12 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor): if not blinded_key: raise ValueError('No signing key is present')
- # 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._decrypt(self.superencrypted, self.revision_counter, subcredential, blinded_key) - self._inner_layer = InnerLayer._decrypt(outer_layer, self.revision_counter, subcredential, blinded_key) + subcredential = hsv3_crypto.get_subcredential(identity_public_key, blinded_key) + + self._outer_layer = OuterLayer._decrypt(self.superencrypted, self.revision_counter, subcredential, blinded_key) + self._inner_layer = InnerLayer._decrypt(self._outer_layer, self.revision_counter, subcredential, blinded_key)
return self._inner_layer
diff --git a/stem/descriptor/hsv3_crypto.py b/stem/descriptor/hsv3_crypto.py index bd090265..ed4ad7b8 100644 --- a/stem/descriptor/hsv3_crypto.py +++ b/stem/descriptor/hsv3_crypto.py @@ -57,3 +57,101 @@ class HSv3PublicBlindedKey(object): """ ext.slow_ed25519.checkvalid(signature, message, self.public_key)
+""" +subcredential + + subcredential = H("subcredential" | credential | blinded-public-ke + credential = H("credential" | public-identity-key) +""" +def get_subcredential(public_identity_key, blinded_key): + cred_bytes_constant = "credential".encode() + subcred_bytes_constant = "subcredential".encode() + + credential = hashlib.sha3_256(b"%s%s" % (cred_bytes_constant, public_identity_key)).digest() + subcredential = hashlib.sha3_256(b"%s%s%s" % (subcred_bytes_constant, credential, blinded_key)).digest() + + return subcredential + + +""" +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 + +""" + +Descriptor encryption + +""" + +def pack(val): + return struct.pack('>Q', val) + + +def get_desc_keys(secret_data, string_constant, + subcredential, revision_counter, salt): + """ + 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 + + where + + 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" + """ + secret_input = b"%s%s%s" % (secret_data, subcredential, pack(revision_counter)) + + kdf = hashlib.shake_256(secret_input + salt + string_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:] + + return secret_key, secret_iv, mac_key + +def get_desc_encryption_mac(key, salt, ciphertext): + + mac = hashlib.sha3_256(pack(len(key)) + key + pack(len(salt)) + salt + ciphertext).digest() + return mac +