[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Refactor IntroductionPointV3 to be able to facilitate encoding/decoding.
commit 1475f12d5afa60d21b9b688e2324bca754a60077
Author: George Kadianakis <desnacked@xxxxxxxxxx>
Date: Thu Oct 10 20:46:10 2019 +0300
Refactor IntroductionPointV3 to be able to facilitate encoding/decoding.
- Changed the attributes of IntroductionPointV3 to be the actual crypto objects
stored instead of strings.
- This allows us to pass introduction point objects from onionbalance to stem,
so that stem can encode them in the descriptor.
- Implement encoding of introduction points.
- Refactor the intro point parsing to support the new attributes.
---
stem/descriptor/certificate.py | 17 +++++
stem/descriptor/hidden_service.py | 153 +++++++++++++++++++++++++++++++++-----
2 files changed, 152 insertions(+), 18 deletions(-)
diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index ac6d455f..cdc44e53 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -253,6 +253,23 @@ class Ed25519CertificateV1(Ed25519Certificate):
return datetime.datetime.now() > self.expiration
+ def certified_ed25519_key(self):
+ """
+ Provide this certificate's certified ed25519 key (the one that got signed)
+
+ :returns: **Ed25519PublicKey**
+
+ :raises: **ValueError** if it's not an ed25519 cert
+ """
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+
+ # Make sure it's an ed25519 cert
+ if (self.key_type != 1):
+ raise ValueError("Certificate is not an ed25519 cert (%d)" % key_type)
+
+ ed_key = Ed25519PublicKey.from_public_bytes(self.key)
+ return ed_key
+
def signing_key(self):
"""
Provides this certificate's signing key.
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 3277e183..4a75bc88 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -138,24 +138,132 @@ class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTI
"""
-class IntroductionPointV3(collections.namedtuple('IntroductionPointV3', ['link_specifiers', 'onion_key', 'auth_key', 'enc_key', 'enc_key_cert', 'legacy_key', 'legacy_key_cert'])):
+class IntroductionPointV3(object):
"""
Introduction point for a v3 hidden service.
+ We want this class to satisfy two use cases:
+
+ - Parsing introduction points directly from the HSv3 descriptor and saving
+ their data here.
+
+ - Creating introduction points for inclusion to an HSv3 descriptor at a point
+ where a descriptor signing key is not yet known (because the descriptor is
+ not yet made). In which case, the certificates cannot be created yet and
+ hence need to be created at encoding time.
+
.. 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
- :var str enc_key: introduction request encryption key
- :var str enc_key_cert: cross-certifier of the signing key by the encryption key
- :var str legacy_key: legacy introduction point RSA public key
- :var str legacy_key_cert: cross-certifier of the signing key by the legacy key
+ :var X25519PublicKey onion_key: ntor introduction point public key
+ :var Ed25519PublicKey auth_key: ed25519 authentication key for this intro point
+ :var stem.certificate.Ed25519Certificate auth_key_cert: cross-certifier of the signing key with the auth key
+ :var X25519PublicKey enc_key: introduction request encryption key
+ :var stem.certificate.Ed25519Certificate enc_key_cert: cross-certifier of the signing key by the encryption key
+ :var XXX legacy_key: legacy introduction point RSA public key
+ :var stem.certificate.Ed25519Certificate legacy_key_cert: cross-certifier of the signing key by the legacy key
+
+ :var Ed25519Certificate descriptor_signing_key: hsv3 descriptor signing key (needed to encode the intro point)
"""
+ def __init__(self, link_specifiers, onion_key, enc_key,
+ auth_key=None, auth_key_cert=None, legacy_key=None, enc_key_cert=None, legacy_key_cert=None):
+ """
+ Initialize this intro point.
+
+ While not all attributes are mandatory, at the very least the link
+ specifiers, the auth key, the onion key and the encryption key need to be
+ provided.
+ The certificates can be left out (for example in the case of creating a new
+ intro point), and they will be created at encode time when the
+ descriptor_signing_key is provided.
+ """
+ if not link_specifiers or not onion_key or not enc_key:
+ raise ValueError("Introduction point missing essential keys")
+
+ if not auth_key and not auth_key_cert:
+ raise ValueError("Either auth key or auth key cert needs to be provided")
+
+ # If we have an auth key cert but not an auth key, extract the key
+ if auth_key_cert and not auth_key:
+ auth_key = auth_key_cert.certified_ed25519_key()
+
+ self.link_specifiers = link_specifiers
+ self.onion_key = enc_key
+ self.enc_key = enc_key
+ self.legacy_key = legacy_key
+ self.auth_key = auth_key
+ self.auth_key_cert = auth_key_cert
+ self.enc_key_cert = enc_key_cert
+ self.legacy_key_cert = legacy_key_cert
+
+ def _encode_link_specifier_block(self):
+ """
+ See BUILDING-BLOCKS in rend-spec-v3.txt
+
+ NSPEC (Number of link specifiers) [1 byte]
+ NSPEC times:
+ LSTYPE (Link specifier type) [1 byte]
+ LSLEN (Link specifier length) [1 byte]
+ LSPEC (Link specifier) [LSLEN bytes]
+ """
+ ls_block = b""
+ ls_block += bytes([len(self.link_specifiers)])
+ for ls in self.link_specifiers:
+ ls_block += ls.encode()
+
+ return base64.b64encode(ls_block)
+
+ def encode(self, descriptor_signing_privkey):
+ """
+ Encode this introduction point into bytes
+ """
+ if not descriptor_signing_privkey:
+ raise ValueError("Cannot encode: Descriptor signing key not provided")
+
+ cert_expiration_date = datetime.datetime.utcnow() + datetime.timedelta(hours=54)
+
+ body = b""
+
+ body += b"introduction-point %s\n" % (self._encode_link_specifier_block())
+
+ # Onion key
+ onion_key_bytes = self.onion_key.public_bytes(encoding=serialization.Encoding.Raw,
+ format=serialization.PublicFormat.Raw)
+ body += b"onion-key ntor %s\n" % (base64.b64encode(onion_key_bytes))
+
+ # Build auth key certificate
+ auth_key_cert = stem.descriptor.certificate.MyED25519Certificate(cert_type=CertType.HS_V3_INTRO_AUTH,
+ expiration_date=cert_expiration_date,
+ cert_key_type=1, certified_pub_key=self.auth_key,
+ signing_priv_key=descriptor_signing_privkey,
+ include_signing_key=True)
+ auth_key_cert_b64_blob = auth_key_cert.encode_for_descriptor()
+ body += b"auth-key\n%s\n" % (auth_key_cert_b64_blob)
+
+ # Build enc key line
+ enc_key_bytes = self.enc_key.public_bytes(encoding=serialization.Encoding.Raw,
+ format=serialization.PublicFormat.Raw)
+ body += b"enc-key ntor %s\n" % (base64.b64encode(enc_key_bytes))
+
+ # Build enc key cert (this does not actually need to certify anything because of #29583)
+ enc_key_cert = stem.descriptor.certificate.MyED25519Certificate(cert_type=CertType.HS_V3_INTRO_ENC,
+ expiration_date=cert_expiration_date,
+ cert_key_type=1, certified_pub_key=self.auth_key,
+ signing_priv_key=descriptor_signing_privkey,
+ include_signing_key=True)
+ enc_key_cert_b64_blob = enc_key_cert.encode_for_descriptor()
+ body += b"enc-key-cert\n%s\n" % (enc_key_cert_b64_blob)
+
+ # We are called to encode legacy key, but we don't know how
+ # TODO do legacy keys!
+ if self.legacy_key or self.legacy_key_cert:
+ raise NotImplementedError
+
+ return body
class AuthorizedClient(collections.namedtuple('AuthorizedClient', ['id', 'iv', 'cookie'])):
- """
+ """
Client authorized to use a v3 hidden service.
.. versionadded:: 1.8.0
@@ -305,6 +413,8 @@ def _parse_v3_inner_formats(descriptor, entries):
def _parse_v3_introduction_points(descriptor, entries):
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
+
if hasattr(descriptor, '_unparsed_introduction_points'):
introduction_points = []
remaining = descriptor._unparsed_introduction_points
@@ -323,33 +433,40 @@ def _parse_v3_introduction_points(descriptor, entries):
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
+ onion_key_b64 = onion_key_line[5:] if onion_key_line.startswith('ntor ') else None
+ onion_key = X25519PublicKey.from_public_bytes(base64.b64decode(onion_key_b64))
- _, block_type, auth_key = entry['auth-key'][0]
+ _, block_type, auth_key_cert = entry['auth-key'][0]
+ auth_key_cert = Ed25519Certificate.parse(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
+ enc_key_b64 = enc_key_line[5:] if enc_key_line.startswith('ntor ') else None
+ enc_key = X25519PublicKey.from_public_bytes(base64.b64decode(enc_key_b64))
_, block_type, enc_key_cert = entry['enc-key-cert'][0]
+ enc_key_cert = Ed25519Certificate.parse(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
+# if legacy_key_cert:
+# legacy_key_cert = Ed25519Certificate.parse(legacy_key_cert)
+
introduction_points.append(
IntroductionPointV3(
- link_specifiers,
- onion_key,
- auth_key,
- enc_key,
- enc_key_cert,
- legacy_key,
- legacy_key_cert,
+ link_specifiers=link_specifiers,
+ onion_key=onion_key,
+ auth_key_cert=auth_key_cert,
+ enc_key=enc_key,
+ enc_key_cert=enc_key_cert,
+ legacy_key=legacy_key,
+ legacy_key_cert=legacy_key_cert,
)
)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits