[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Sign created extrainfo descriptors
commit 05f0dc8763e18f7aa0c3aca8d209c1a901ffaf94
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Wed Jun 28 09:24:46 2017 -0700
Sign created extrainfo descriptors
Extrainfo descriptors are signed the same way as server descriptors so simple
to add support. Good opportunity too for generalizing this.
---
stem/descriptor/__init__.py | 81 ++++++++++++++++++++++++++++
stem/descriptor/extrainfo_descriptor.py | 17 ++++--
stem/descriptor/server_descriptor.py | 57 +++++---------------
test/unit/descriptor/extrainfo_descriptor.py | 2 +-
4 files changed, 108 insertions(+), 49 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 80901af..21e7bd7 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -39,6 +39,7 @@ Package for parsing and processing descriptor data.
import base64
import codecs
+import collections
import copy
import hashlib
import os
@@ -99,6 +100,18 @@ DocumentHandler = stem.util.enum.UppercaseEnum(
)
+class SigningKey(collections.namedtuple('SigningKey', ['private', 'public', 'public_digest'])):
+ """
+ Key used by relays to sign their server and extrainfo descriptors.
+
+ .. versionadded:: 1.6.0
+
+ :var cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey private: private key
+ :var cryptography.hazmat.backends.openssl.rsa._RSAPublicKey public: public key
+ :var bytes public_digest: block that can be used for the a server descrptor's 'signing-key' field
+ """
+
+
def parse_file(descriptor_file, descriptor_type = None, validate = False, document_handler = DocumentHandler.ENTRIES, normalize_newlines = None, **kwargs):
"""
Simple function to read the descriptor contents from a file, providing an
@@ -953,6 +966,74 @@ def _get_pseudo_pgp_block(remaining_contents):
return None
+def _signing_key(private_key = None):
+ """
+ Serializes a signing key if we have one. Otherwise this creates a new signing
+ key we can use to create descriptors.
+
+ :param cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey private_key: private key
+
+ :returns: :class:`~stem.descriptor.__init__.SigningKey` that can be used to
+ create descriptors
+ """
+
+ if not stem.prereq.is_crypto_available():
+ raise ImportError('Signing requires the cryptography module')
+
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives import serialization
+ from cryptography.hazmat.primitives.asymmetric import rsa
+
+ if private_key is None:
+ private_key = rsa.generate_private_key(
+ public_exponent = 65537,
+ key_size = 1024,
+ backend = default_backend(),
+ )
+
+ # When signing the cryptography module includes a constant indicating
+ # the hash algorithm used. Tor doesn't. This causes signature
+ # validation failures and unfortunately cryptography have no nice way
+ # of excluding these so we need to mock out part of their internals...
+ #
+ # https://github.com/pyca/cryptography/issues/3713
+
+ def no_op(*args, **kwargs):
+ return 1
+
+ private_key._backend._lib.EVP_PKEY_CTX_set_signature_md = no_op
+ private_key._backend.openssl_assert = no_op
+
+ public_key = private_key.public_key()
+ public_digest = b'\n' + public_key.public_bytes(
+ encoding = serialization.Encoding.PEM,
+ format = serialization.PublicFormat.PKCS1,
+ ).strip()
+
+ return SigningKey(private_key, public_key, public_digest)
+
+
+def _append_router_signature(content, private_key):
+ """
+ Appends a router signature to a server or extrainfo descriptor.
+
+ :param bytes content: descriptor content up through 'router-signature\\n'
+ :param cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey private_key:
+ private relay signing key
+
+ :returns: **bytes** with the signed descriptor content
+ """
+
+ if not stem.prereq.is_crypto_available():
+ raise ImportError('Signing requires the cryptography module')
+
+ from cryptography.hazmat.primitives import hashes
+ from cryptography.hazmat.primitives.asymmetric import padding
+
+ signature = base64.b64encode(private_key.sign(content, padding.PKCS1v15(), hashes.SHA1()))
+ return content + b'\n'.join([b'-----BEGIN SIGNATURE-----'] + stem.util.str_tools._split_by_length(signature, 64) + [b'-----END SIGNATURE-----\n'])
+
+
def _random_ipv4_address():
return '%i.%i.%i.%i' % (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index c0484ad..3b4e9a4 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -89,6 +89,8 @@ from stem.descriptor import (
_parse_timestamp_line,
_parse_forty_character_hex,
_parse_key_block,
+ _signing_key,
+ _append_router_signature,
)
try:
@@ -973,11 +975,20 @@ class RelayExtraInfoDescriptor(ExtraInfoDescriptor):
})
@classmethod
- def content(cls, attr = None, exclude = (), sign = False):
+ def content(cls, attr = None, exclude = (), sign = False, private_signing_key = None):
if sign:
- raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
+ if attr and 'router-signature' in attr:
+ raise ValueError('Cannot sign the descriptor if a router-signature has been provided')
- return _descriptor_content(attr, exclude, sign, RELAY_EXTRAINFO_HEADER, RELAY_EXTRAINFO_FOOTER)
+ signing_key = _signing_key(private_signing_key)
+ content = _descriptor_content(attr, exclude, sign, RELAY_EXTRAINFO_HEADER) + b'\nrouter-signature\n'
+ return _append_router_signature(content, signing_key.private)
+ else:
+ return _descriptor_content(attr, exclude, sign, RELAY_EXTRAINFO_HEADER, RELAY_EXTRAINFO_FOOTER)
+
+ @classmethod
+ def create(cls, attr = None, exclude = (), validate = True, sign = False, private_signing_key = None):
+ return cls(cls.content(attr, exclude, sign, private_signing_key), validate = validate)
@lru_cache()
def digest(self):
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 6ffe914..580b3d3 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -66,6 +66,8 @@ from stem.descriptor import (
_parse_forty_character_hex,
_parse_protocol_line,
_parse_key_block,
+ _signing_key,
+ _append_router_signature,
_random_ipv4_address,
_random_date,
_random_crypto_blob,
@@ -801,6 +803,9 @@ class RelayDescriptor(ServerDescriptor):
@classmethod
def content(cls, attr = None, exclude = (), sign = False, private_signing_key = None):
+ if attr is None:
+ attr = {}
+
base_header = (
('router', 'Unnamed%i %s 9001 0 0' % (random.randint(0, sys.maxint), _random_ipv4_address())),
('published', _random_date()),
@@ -810,59 +815,21 @@ class RelayDescriptor(ServerDescriptor):
('signing-key', _random_crypto_blob('RSA PUBLIC KEY')),
)
- base_footer = (
- ('router-signature', _random_crypto_blob('SIGNATURE')),
- )
-
if sign:
- if not stem.prereq.is_crypto_available():
- raise ImportError('Signing requires the cryptography module')
- elif attr and 'signing-key' in attr:
+ if attr and 'signing-key' in attr:
raise ValueError('Cannot sign the descriptor if a signing-key has been provided')
elif attr and 'router-signature' in attr:
raise ValueError('Cannot sign the descriptor if a router-signature has been provided')
- from cryptography.hazmat.backends import default_backend
- from cryptography.hazmat.primitives import hashes, serialization
- from cryptography.hazmat.primitives.asymmetric import padding, rsa
-
- if attr is None:
- attr = {}
-
- if private_signing_key is None:
- private_signing_key = rsa.generate_private_key(
- public_exponent = 65537,
- key_size = 1024,
- backend = default_backend(),
- )
-
- # When signing the cryptography module includes a constant indicating
- # the hash algorithm used. Tor doesn't. This causes signature
- # validation failures and unfortunately cryptography have no nice way
- # of excluding these so we need to mock out part of their internals...
- #
- # https://github.com/pyca/cryptography/issues/3713
-
- def no_op(*args, **kwargs):
- return 1
-
- private_signing_key._backend._lib.EVP_PKEY_CTX_set_signature_md = no_op
- private_signing_key._backend.openssl_assert = no_op
-
- # create descriptor content without the router-signature, then
- # appending the content signature
-
- attr['signing-key'] = b'\n' + private_signing_key.public_key().public_bytes(
- encoding = serialization.Encoding.PEM,
- format = serialization.PublicFormat.PKCS1,
- ).strip()
+ signing_key = _signing_key(private_signing_key)
+ attr['signing-key'] = signing_key.public_digest
content = _descriptor_content(attr, exclude, sign, base_header) + b'\nrouter-signature\n'
- signature = base64.b64encode(private_signing_key.sign(content, padding.PKCS1v15(), hashes.SHA1()))
-
- return content + b'\n'.join([b'-----BEGIN SIGNATURE-----'] + stem.util.str_tools._split_by_length(signature, 64) + [b'-----END SIGNATURE-----\n'])
+ return _append_router_signature(content, signing_key.private)
else:
- return _descriptor_content(attr, exclude, sign, base_header, base_footer)
+ return _descriptor_content(attr, exclude, sign, base_header, (
+ ('router-signature', _random_crypto_blob('SIGNATURE')),
+ ))
@classmethod
def create(cls, attr = None, exclude = (), validate = True, sign = False, private_signing_key = None):
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py
index d41f68b..1f91e72 100644
--- a/test/unit/descriptor/extrainfo_descriptor.py
+++ b/test/unit/descriptor/extrainfo_descriptor.py
@@ -136,7 +136,7 @@ k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw
@test.require.cryptography
def test_descriptor_signing(self):
- self.assertRaisesRegexp(NotImplementedError, 'Signing of RelayExtraInfoDescriptor not implemented', RelayExtraInfoDescriptor.create, sign = True)
+ RelayExtraInfoDescriptor.create(sign = True)
self.assertRaisesRegexp(NotImplementedError, 'Signing of BridgeExtraInfoDescriptor not implemented', BridgeExtraInfoDescriptor.create, sign = True)
def test_multiple_metrics_bridge_descriptors(self):
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits