[tor-commits] [stem/master] Add initial support for parsing v3 addresses.

atagar at torproject.org atagar at torproject.org
Sun Oct 6 02:07:34 UTC 2019


commit f815223fc2e7d2e460c18a90d66d0b4128eb9f23
Author: George Kadianakis <desnacked at riseup.net>
Date:   Tue Sep 3 14:03:04 2019 +0300

    Add initial support for parsing v3 addresses.
---
 stem/descriptor/hidden_service.py | 10 ++++++-
 stem/descriptor/hsv3_crypto.py    | 57 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 52e1b0b1..e6809306 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -526,10 +526,18 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
   def create(cls, attr = None, exclude = (), validate = True, sign = False):
     return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
 
-  def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
+  def __init__(self, raw_contents, validate = False, onion_address = None, skip_crypto_validation = False):
+    """
+    The onion_address is needed so that we can decrypt the descriptor, which is
+    impossible without the full onion address.
+    """
     super(HiddenServiceDescriptorV3, self).__init__(raw_contents, lazy_load = not validate)
     entries = _descriptor_components(raw_contents, validate)
 
+    if onion_address == None:
+      raise ValueError("The onion address MUST be provided to parse a V3 descriptor")
+    self.onion_address = onion_address
+
     if validate:
       for keyword in REQUIRED_V3_FIELDS:
         if keyword not in entries:
diff --git a/stem/descriptor/hsv3_crypto.py b/stem/descriptor/hsv3_crypto.py
new file mode 100644
index 00000000..0feb0fe0
--- /dev/null
+++ b/stem/descriptor/hsv3_crypto.py
@@ -0,0 +1,57 @@
+import base64
+import hashlib
+
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.backends import default_backend
+
+
+"""
+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
+
+"""
+
+CHECKSUM_CONSTANT = b".onion checksum"
+
+def decode_address(onion_address_str):
+    """
+    Parse onion_address_str and return the pubkey.
+
+         onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
+         CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
+
+    :return: Ed25519PublicKey
+
+    :raises: ValueError
+    """
+    if (len(onion_address_str) != 56 + len(".onion")):
+        raise ValueError("Wrong address length")
+
+    # drop the '.onion'
+    onion_address = onion_address_str[:56]
+
+    # base32 decode the addr (convert to uppercase since that's what python expects)
+    onion_address = base64.b32decode(onion_address.upper())
+    assert(len(onion_address) == 35)
+
+    # extract pieces of information
+    pubkey = onion_address[:32]
+    checksum = onion_address[32:34]
+    version = onion_address[34]
+
+    # Do checksum validation
+    my_checksum_body = b"%s%s%s" % (CHECKSUM_CONSTANT, pubkey, bytes([version]))
+    my_checksum = hashlib.sha3_256(my_checksum_body).digest()
+
+    if (checksum != my_checksum[:2]):
+        raise ValueError("Bad checksum")
+
+    return Ed25519PublicKey.from_public_bytes(pubkey)





More information about the tor-commits mailing list