[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [stem/master] Validate server descriptor using Ed25519 certificate



commit 905b6217cb57b3952222719d05b4249d29663174
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date:   Tue Mar 28 17:56:40 2017 +0200

    Validate server descriptor using Ed25519 certificate
    
    Finally integrating the cryptographic descriptor validation. Not sure if it
    works or not but looks like it should be equivialent to what we had. Needs
    tests though.
---
 stem/descriptor/certificate.py       | 52 ++++++++++++++++++++++++++++++++++++
 stem/descriptor/server_descriptor.py | 33 ++++++++++++++---------
 2 files changed, 73 insertions(+), 12 deletions(-)

diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 5d60458..eafa51e 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -58,6 +58,7 @@ from stem.util import enum
 
 ED25519_HEADER_LENGTH = 40
 ED25519_SIGNATURE_LENGTH = 64
+ED25519_ROUTER_SIGNATURE_PREFIX = b'Tor router descriptor signature v1'
 
 CertType = enum.UppercaseEnum('SIGNING', 'LINK_CERT', 'AUTH')
 ExtensionType = enum.Enum(('HAS_SIGNING_KEY', 4),)
@@ -186,6 +187,57 @@ class Ed25519CertificateV1(Ed25519Certificate):
 
     return datetime.datetime.now() > self.expiration
 
+  def verify(self, server_descriptor):
+    """
+    Validates our signing key and that the given descriptor content matches its
+    Ed25519 signature.
+
+    :param stem.descriptor.server_descriptor.Ed25519 server_descriptor: relay
+      server descriptor to validate
+
+    :raises:
+      * **ValueError** if signing key or descriptor are invalid
+      * **ImportError** if pynacl module is unavailable
+    """
+
+    if not stem.prereq._is_pynacl_available():
+      raise ImportError('Certificate validation requires the pynacl module')
+
+    import nacl.signing
+    import nacl.encoding
+    from nacl.exceptions import BadSignatureError
+
+    descriptor_content = server_descriptor.get_bytes()
+    signing_key = server_descriptor.ed25519_master_key
+
+    if not signing_key:
+      for extension in self.extensions:
+        if extension.type == ExtensionType.HAS_SIGNING_KEY:
+          signing_key = extension.data
+          break
+
+    if not signing_key:
+      raise ValueError('Server descriptor missing an ed25519 signing key')
+
+    try:
+      verify_key = nacl.signing.VerifyKey(signing_key + '=', encoder = nacl.encoding.Base64Encoder)
+      verify_key.verify(descriptor_content[:-ED25519_SIGNATURE_LENGTH], self.signature)
+    except BadSignatureError as exc:
+      raise ValueError('Ed25519KeyCertificate signing key is invalid (%s)' % exc)
+
+    # ed25519 signature validates descriptor content up until the signature itself
+
+    signed_content = descriptor_content[:descriptor_content.index(b'router-sig-ed25519 ') + 19]
+    descriptor_sha256_digest = hashlib.sha256(ED25519_ROUTER_SIGNATURE_PREFIX + signed_content).digest()
+
+    missing_padding = len(server_descriptor.ed25519_signature) % 4
+    signature_bytes = base64.b64decode(stem.util.str_tools._to_bytes(server_descriptor.ed25519_signature) + b'=' * missing_padding)
+
+    try:
+      verify_key = nacl.signing.VerifyKey(self.key)
+      verify_key.verify(descriptor_sha256_digest, signature_bytes)
+    except BadSignatureError as exc:
+      raise ValueError('Descriptor Ed25519 certificate signature invalid (%s)' % exc)
 
 
 class Ed25519Extension(collections.namedtuple('Ed25519Extension', ['type', 'flags', 'flag_int', 'data'])):
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index d309f11..2501b0e 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -37,6 +37,7 @@ import functools
 import hashlib
 import re
 
+import stem.descriptor.certificate
 import stem.descriptor.extrainfo_descriptor
 import stem.exit_policy
 import stem.prereq
@@ -64,8 +65,6 @@ from stem.descriptor import (
   _parse_key_block,
 )
 
-from stem.descriptor.certificate import _parse_certificate
-
 try:
   # added in python 3.2
   from functools import lru_cache
@@ -390,7 +389,11 @@ def _parse_exit_policy(descriptor, entries):
     del descriptor._unparsed_exit_policy
 
 
-_parse_identity_ed25519_line = _parse_key_block('identity-ed25519', 'ed25519_certificate', 'ED25519 CERT')
+def _parse_identity_ed25519_line(descriptor, entries):
+  _parse_key_block('identity-ed25519', 'ed25519_certificate', 'ED25519 CERT')(descriptor, entries)
+  descriptor.certificate = stem.descriptor.certificate.Ed25519Certificate.parse(descriptor.ed25519_certificate)
+
+
 _parse_master_key_ed25519_line = _parse_simple_line('master-key-ed25519', 'ed25519_master_key')
 _parse_master_key_ed25519_for_hash_line = _parse_simple_line('master-key-ed25519', 'ed25519_certificate_hash')
 _parse_contact_line = _parse_bytes_line('contact', 'contact')
@@ -696,6 +699,7 @@ class RelayDescriptor(ServerDescriptor):
   Server descriptor (`descriptor specification
   <https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt>`_)
 
+  :var stem.certificate.Ed25519Certificate certificate: ed25519 certificate
   :var str ed25519_certificate: base64 encoded ed25519 certificate
   :var str ed25519_master_key: base64 encoded master key for our ed25519 certificate
   :var str ed25519_signature: signature of this document using ed25519
@@ -718,9 +722,18 @@ class RelayDescriptor(ServerDescriptor):
      Moved from the deprecated `pycrypto
      <https://www.dlitz.net/software/pycrypto/>`_ module to `cryptography
      <https://pypi.python.org/pypi/cryptography>`_ for validating signatures.
+
+  .. versionchanged:: 1.6.0
+     Added the certificate attribute.
+
+  .. deprecated:: 1.6.0
+     Our **ed25519_certificate** is deprecated in favor of our new
+     **certificate** attribute. The base64 encoded certificate is available via
+     the certificate's **encoded** attribute.
   """
 
   ATTRIBUTES = dict(ServerDescriptor.ATTRIBUTES, **{
+    'certificate': (None, _parse_identity_ed25519_line),
     'ed25519_certificate': (None, _parse_identity_ed25519_line),
     'ed25519_master_key': (None, _parse_master_key_ed25519_line),
     'ed25519_signature': (None, _parse_router_sig_ed25519_line),
@@ -766,10 +779,8 @@ class RelayDescriptor(ServerDescriptor):
           if onion_key_crosscert_digest != self.onion_key_crosscert_digest():
             raise ValueError('Decrypted onion-key-crosscert digest does not match local digest (calculated: %s, local: %s)' % (onion_key_crosscert_digest, self.onion_key_crosscert_digest()))
 
-      if stem.prereq._is_pynacl_available() and self.ed25519_certificate:
-        self.certificate = _parse_certificate(_bytes_for_block(self.ed25519_certificate), self.ed25519_master_key, validate)
-
-        self.certificate.verify_descriptor_signature(raw_contents, self.ed25519_signature)
+      if stem.prereq._is_pynacl_available() and self.certificate:
+        self.certificate.validate(self)
 
   @lru_cache()
   def digest(self):
@@ -786,12 +797,10 @@ class RelayDescriptor(ServerDescriptor):
   @lru_cache()
   def onion_key_crosscert_digest(self):
     """
-    Provides the digest of the onion-key-crosscert data consisting of the following:
-
-    1. SHA1 digest of the RSA identity key
-    2. the ed25519 identity key
+    Provides the digest of the onion-key-crosscert data. This consists of the
+    RSA identity key sha1 and ed25519 identity key.
 
-    :returns: the digest encoded in uppercase hex
+    :returns: **unicode** digest encoded in uppercase hex
 
     :raises: ValueError if the digest cannot be calculated
     """



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits