[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Move HSv3 certificate validation
commit 0881dcc003ef29272f514778af71b368de5be82e
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Tue Oct 15 18:29:37 2019 -0700
Move HSv3 certificate validation
Adding HSv3 cert validation to our Ed25519CertificateV1's validate() method.
Pretty similar to server descriptor validation.
---
stem/descriptor/certificate.py | 83 ++++++++++++++++++++++------------
stem/descriptor/hidden_service.py | 25 ++--------
stem/descriptor/router_status_entry.py | 7 +--
stem/util/str_tools.py | 12 +++++
test/unit/descriptor/certificate.py | 2 +-
5 files changed, 72 insertions(+), 57 deletions(-)
diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 514f3155..05e0eefb 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -73,8 +73,10 @@ import binascii
import collections
import datetime
import hashlib
+import re
import stem.prereq
+import stem.descriptor.hidden_service
import stem.descriptor.server_descriptor
import stem.util.enum
import stem.util.str_tools
@@ -85,7 +87,6 @@ from cryptography.hazmat.primitives import serialization
ED25519_HEADER_LENGTH = 40
ED25519_SIGNATURE_LENGTH = 64
-ED25519_ROUTER_SIGNATURE_PREFIX = b'Tor router descriptor signature v1'
CertType = stem.util.enum.UppercaseEnum(
'RESERVED_0', 'RESERVED_1', 'RESERVED_2', 'RESERVED_3',
@@ -272,59 +273,83 @@ class Ed25519CertificateV1(Ed25519Certificate):
def validate(self, descriptor):
"""
- Validates our signing key and that the given descriptor content matches its
- Ed25519 signature. Supported descriptor types include...
+ Validate our descriptor content matches its ed25519 signature. Supported
+ descriptor types include...
- * server descriptors
+ * :class:`~stem.descriptor.server_descriptor.RelayDescriptor`
+ * :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptorV3`
:param stem.descriptor.__init__.Descriptor descriptor: descriptor to validate
:raises:
* **ValueError** if signing key or descriptor are invalid
- * **ImportError** if cryptography module is unavailable or ed25519 is
- unsupported
+ * **TypeError** if descriptor type is unsupported
+ * **ImportError** if cryptography module or ed25519 support unavailable
"""
if not stem.prereq._is_crypto_ed25519_supported():
raise ImportError('Certificate validation requires the cryptography module and ed25519 support')
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
- from cryptography.exceptions import InvalidSignature
+ if isinstance(descriptor, stem.descriptor.server_descriptor.RelayDescriptor):
+ signed_content = hashlib.sha256(Ed25519CertificateV1._signed_content(descriptor)).digest()
+ signature = stem.util.str_tools._decode_b64(descriptor.ed25519_signature)
- if not isinstance(descriptor, stem.descriptor.server_descriptor.RelayDescriptor):
- raise ValueError('Certificate validation only supported for server descriptors, not %s' % type(descriptor).__name__)
-
- if descriptor.ed25519_master_key:
- signing_key = base64.b64decode(stem.util.str_tools._to_bytes(descriptor.ed25519_master_key) + b'=')
+ self._validate_server_desc_signing_key(descriptor)
+ elif isinstance(descriptor, stem.descriptor.hidden_service.HiddenServiceDescriptorV3):
+ signed_content = Ed25519CertificateV1._signed_content(descriptor)
+ signature = stem.util.str_tools._decode_b64(descriptor.signature)
else:
- signing_key = self.signing_key()
+ raise TypeError('Certificate validation only supported for server and hidden service descriptors, not %s' % type(descriptor).__name__)
- if not signing_key:
- raise ValueError('Server descriptor missing an ed25519 signing key')
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+ from cryptography.exceptions import InvalidSignature
try:
- Ed25519PublicKey.from_public_bytes(signing_key).verify(self.signature, base64.b64decode(stem.util.str_tools._to_bytes(self.encoded))[:-ED25519_SIGNATURE_LENGTH])
+ key = Ed25519PublicKey.from_public_bytes(self.key)
+ key.verify(signature, signed_content)
except InvalidSignature:
- raise ValueError('Ed25519KeyCertificate signing key is invalid (Signature was forged or corrupt)')
+ raise ValueError('Descriptor Ed25519 certificate signature invalid (signature forged or corrupt)')
- # ed25519 signature validates descriptor content up until the signature itself
+ @staticmethod
+ def _signed_content(descriptor):
+ """
+ Provides this descriptor's signing constant, appended with the portion of
+ the descriptor that's signed.
+ """
- descriptor_content = descriptor.get_bytes()
+ if isinstance(descriptor, stem.descriptor.server_descriptor.RelayDescriptor):
+ prefix = b'Tor router descriptor signature v1'
+ regex = '(.+router-sig-ed25519 )'
+ elif isinstance(descriptor, stem.descriptor.hidden_service.HiddenServiceDescriptorV3):
+ prefix = b'Tor onion service descriptor sig v3'
+ regex = '(.+)signature '
+ else:
+ raise ValueError('BUG: %s type unexpected' % type(descriptor).__name__)
+
+ match = re.search(regex, descriptor.get_bytes(), re.DOTALL)
+
+ if not match:
+ raise ValueError('Malformed descriptor missing signature line')
- if b'router-sig-ed25519 ' not in descriptor_content:
- raise ValueError("Descriptor doesn't have a router-sig-ed25519 entry.")
+ return prefix + match.group(1)
+
+ def _validate_server_desc_signing_key(self, descriptor):
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+ from cryptography.exceptions import InvalidSignature
- signed_content = descriptor_content[:descriptor_content.index(b'router-sig-ed25519 ') + 19]
- descriptor_sha256_digest = hashlib.sha256(ED25519_ROUTER_SIGNATURE_PREFIX + signed_content).digest()
+ if descriptor.ed25519_master_key:
+ signing_key = base64.b64decode(stem.util.str_tools._to_bytes(descriptor.ed25519_master_key) + b'=')
+ else:
+ signing_key = self.signing_key()
- missing_padding = len(descriptor.ed25519_signature) % 4
- signature_bytes = base64.b64decode(stem.util.str_tools._to_bytes(descriptor.ed25519_signature) + b'=' * missing_padding)
+ if not signing_key:
+ raise ValueError('Server descriptor missing an ed25519 signing key')
try:
- verify_key = Ed25519PublicKey.from_public_bytes(self.key)
- verify_key.verify(signature_bytes, descriptor_sha256_digest)
+ key = Ed25519PublicKey.from_public_bytes(signing_key)
+ key.verify(self.signature, base64.b64decode(stem.util.str_tools._to_bytes(self.encoded))[:-ED25519_SIGNATURE_LENGTH])
except InvalidSignature:
- raise ValueError('Descriptor Ed25519 certificate signature invalid (Signature was forged or corrupt)')
+ raise ValueError('Ed25519KeyCertificate signing key is invalid (signature forged or corrupt)')
class MyED25519Certificate(object):
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 7c279629..49009a6a 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -655,8 +655,7 @@ class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
raise DecryptionFailure('Decrypting introduction-points requires the cryptography module')
try:
- missing_padding = len(authentication_cookie) % 4
- authentication_cookie = base64.b64decode(stem.util.str_tools._to_bytes(authentication_cookie) + b'=' * missing_padding)
+ authentication_cookie = stem.util.str_tools._decode_b64(authentication_cookie)
except TypeError as exc:
raise DecryptionFailure('authentication_cookie must be a base64 encoded string (%s)' % exc)
@@ -1054,28 +1053,12 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
raise ValueError("Hidden service descriptor must end with a 'signature' entry")
self._parse(entries, validate)
+
+ if self.signing_cert:
+ self.signing_cert.validate(self)
else:
self._entries = entries
- from cryptography.hazmat.backends.openssl.backend import backend
-
- if backend.x25519_supported() and self.signing_cert:
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
-
- # Verify the signature!
- # First compute the body that was signed
-
- descriptor_signing_key = Ed25519PublicKey.from_public_bytes(self.signing_cert.key)
- descriptor_body = raw_contents.split(b'signature')[0] # everything before the signature
- signature_body = b'Tor onion service descriptor sig v3' + descriptor_body
-
- # Decode base64 signature
- missing_padding = len(self.signature) % 4
- signature = base64.b64decode(self.signature + '=' * missing_padding)
-
- # Verify signature
- descriptor_signing_key.verify(signature, signature_body)
-
def decrypt(self, onion_address):
"""
Decrypt this descriptor. Hidden serice descriptors contain two encryption
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index ce662b40..91f2c146 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -21,7 +21,6 @@ sources...
+- RouterStatusEntryMicroV3 - Entry for a microdescriptor flavored v3 document
"""
-import base64
import binascii
import io
@@ -369,12 +368,8 @@ def _base64_to_hex(identity, check_if_fingerprint = True):
:raises: **ValueError** if the result isn't a valid fingerprint
"""
- # trailing equal signs were stripped from the identity
- missing_padding = len(identity) % 4
- identity += '=' * missing_padding
-
try:
- identity_decoded = base64.b64decode(stem.util.str_tools._to_bytes(identity))
+ identity_decoded = stem.util.str_tools._decode_b64(stem.util.str_tools._to_bytes(identity))
except (TypeError, binascii.Error):
raise ValueError("Unable to decode identity string '%s'" % identity)
diff --git a/stem/util/str_tools.py b/stem/util/str_tools.py
index aba619b9..869f46b6 100644
--- a/stem/util/str_tools.py
+++ b/stem/util/str_tools.py
@@ -21,6 +21,7 @@ Toolkit for various string activity.
parse_short_time_label - seconds represented by a short time label
"""
+import base64
import codecs
import datetime
import re
@@ -116,6 +117,17 @@ def _to_unicode(msg):
return _to_unicode_impl(msg)
+def _decode_b64(msg):
+ """
+ Base64 decode, without padding concerns.
+ """
+
+ missing_padding = len(msg) % 4
+ padding_chr = b'=' if isinstance(msg, bytes) else '='
+
+ return base64.b64decode(msg + padding_chr * missing_padding)
+
+
def _to_int(msg):
"""
Serializes a string to a number.
diff --git a/test/unit/descriptor/certificate.py b/test/unit/descriptor/certificate.py
index d86c169a..6d2fd378 100644
--- a/test/unit/descriptor/certificate.py
+++ b/test/unit/descriptor/certificate.py
@@ -194,7 +194,7 @@ class TestEd25519Certificate(unittest.TestCase):
desc = next(stem.descriptor.parse_file(descriptor_file, validate = False))
cert = Ed25519Certificate.parse(certificate())
- self.assertRaisesWith(ValueError, 'Ed25519KeyCertificate signing key is invalid (Signature was forged or corrupt)', cert.validate, desc)
+ self.assertRaisesWith(ValueError, 'Ed25519KeyCertificate signing key is invalid (signature forged or corrupt)', cert.validate, desc)
@test.require.ed25519_support
def test_encode_decode_certificate(self):
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits