[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Start using the cryptography package for verifying Ed25519 signatures
commit f58577435c91b56f9381f07cb6486a17f9b7ad9a
Author: Illia Volochii <illia.volochii@xxxxxxxxx>
Date: Wed Apr 10 00:23:34 2019 +0300
Start using the cryptography package for verifying Ed25519 signatures
---
docs/faq.rst | 4 +---
requirements.txt | 1 -
run_tests.py | 1 -
stem/descriptor/certificate.py | 29 ++++++++++++++---------------
stem/descriptor/server_descriptor.py | 2 +-
stem/prereq.py | 26 ++++++++++++++------------
test/require.py | 3 +--
test/settings.cfg | 2 --
test/task.py | 2 --
test/unit/descriptor/certificate.py | 6 +++---
10 files changed, 34 insertions(+), 42 deletions(-)
diff --git a/docs/faq.rst b/docs/faq.rst
index 23d0c3f1..ceaeaf78 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -53,8 +53,7 @@ Does Stem have any dependencies?
**No.** All you need in order to use Stem is Python.
When it is available Stem will use `cryptography
-<https://pypi.python.org/pypi/cryptography>`_ and `PyNaCl
-<https://pypi.python.org/pypi/PyNaCl/>`_ to validate descriptor signatures.
+<https://pypi.python.org/pypi/cryptography>`_ to validate descriptor signatures.
However, there is no need to install cryptography unless you need this
functionality.
@@ -81,7 +80,6 @@ Ubuntu you can install these with...
% sudo apt-get install python-dev libffi-dev
% sudo pip install cryptography
- % sudo pip install pynacl
.. _what_python_versions_is_stem_compatible_with:
diff --git a/requirements.txt b/requirements.txt
index 3cd71608..6dc054ca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,3 @@ pyflakes
pycodestyle
tox
cryptography
-pynacl
diff --git a/run_tests.py b/run_tests.py
index e8dd1a82..c31e7b48 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -163,7 +163,6 @@ def main():
test.task.PYTHON_VERSION,
test.task.PLATFORM_VERSION,
test.task.CRYPTO_VERSION,
- test.task.PYNACL_VERSION,
test.task.MOCK_VERSION,
test.task.PYFLAKES_VERSION,
test.task.PYCODESTYLE_VERSION,
diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 37829bc4..2f62e889 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -224,34 +224,33 @@ class Ed25519CertificateV1(Ed25519Certificate):
:raises:
* **ValueError** if signing key or descriptor are invalid
- * **ImportError** if pynacl module is unavailable
+ * **ImportError** if cryptography module is unavailable or ed25519 is not supported
"""
- if not stem.prereq._is_pynacl_available():
- raise ImportError('Certificate validation requires the pynacl module')
+ if not stem.prereq._is_crypto_ed25519_supported():
+ raise ImportError('Certificate validation requires the cryptography module and support of ed25519')
- import nacl.signing
- import nacl.encoding
- from nacl.exceptions import BadSignatureError
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+ from cryptography.exceptions import InvalidSignature
descriptor_content = server_descriptor.get_bytes()
signing_key = None
if server_descriptor.ed25519_master_key:
- signing_key = nacl.signing.VerifyKey(stem.util.str_tools._to_bytes(server_descriptor.ed25519_master_key) + b'=', encoder = nacl.encoding.Base64Encoder)
+ signing_key = Ed25519PublicKey.from_public_bytes(base64.b64decode(stem.util.str_tools._to_bytes(server_descriptor.ed25519_master_key) + b'='))
else:
for extension in self.extensions:
if extension.type == ExtensionType.HAS_SIGNING_KEY:
- signing_key = nacl.signing.VerifyKey(extension.data)
+ signing_key = Ed25519PublicKey.from_public_bytes(extension.data)
break
if not signing_key:
raise ValueError('Server descriptor missing an ed25519 signing key')
try:
- signing_key.verify(base64.b64decode(stem.util.str_tools._to_bytes(self.encoded))[:-ED25519_SIGNATURE_LENGTH], self.signature)
- except BadSignatureError as exc:
- raise ValueError('Ed25519KeyCertificate signing key is invalid (%s)' % exc)
+ signing_key.verify(self.signature, base64.b64decode(stem.util.str_tools._to_bytes(self.encoded))[:-ED25519_SIGNATURE_LENGTH])
+ except InvalidSignature:
+ raise ValueError('Ed25519KeyCertificate signing key is invalid (Signature was forged or corrupt)')
# ed25519 signature validates descriptor content up until the signature itself
@@ -265,7 +264,7 @@ class Ed25519CertificateV1(Ed25519Certificate):
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)
+ verify_key = Ed25519PublicKey.from_public_bytes(self.key)
+ verify_key.verify(signature_bytes, descriptor_sha256_digest)
+ except InvalidSignature:
+ raise ValueError('Descriptor Ed25519 certificate signature invalid (Signature was forged or corrupt)')
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 6cc0e775..fc03a711 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -865,7 +865,7 @@ 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.certificate:
+ if stem.prereq._is_crypto_ed25519_supported() and self.certificate:
self.certificate.validate(self)
@classmethod
diff --git a/stem/prereq.py b/stem/prereq.py
index 594e4239..c2b546dc 100644
--- a/stem/prereq.py
+++ b/stem/prereq.py
@@ -29,7 +29,7 @@ import sys
CRYPTO_UNAVAILABLE = "Unable to import the cryptography module. Because of this we'll be unable to verify descriptor signature integrity. You can get cryptography from: https://pypi.python.org/pypi/cryptography"
ZSTD_UNAVAILABLE = 'ZSTD compression requires the zstandard module (https://pypi.python.org/pypi/zstandard)'
LZMA_UNAVAILABLE = 'LZMA compression requires the lzma module (https://docs.python.org/3/library/lzma.html)'
-PYNACL_UNAVAILABLE = "Unable to import the pynacl module. Because of this we'll be unable to verify descriptor ed25519 certificate integrity. You can get pynacl from https://pypi.python.org/pypi/PyNaCl/"
+ED25519_UNSUPPORTED = "Unable to verify descriptor ed25519 certificate integrity. ed25519 is not supported by installed versions of OpenSSL and/or cryptography"
def check_requirements():
@@ -126,6 +126,7 @@ def is_crypto_available():
try:
from cryptography.utils import int_from_bytes, int_to_bytes
from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.backends.openssl.backend import backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.serialization import load_der_public_key
@@ -239,20 +240,21 @@ def _is_lru_cache_available():
return hasattr(functools, 'lru_cache')
-def _is_pynacl_available():
+def _is_crypto_ed25519_supported():
"""
- Checks if the pynacl functions we use are available. This is used for
- verifying ed25519 certificates in relay descriptor signatures.
+ Checks if ed25519 is supported by current versions of the cryptography
+ package and OpenSSL. This is used for verifying ed25519 certificates in relay
+ descriptor signatures.
- :returns: **True** if we can use pynacl and **False** otherwise
+ :returns: **True** if ed25519 is supported and **False** otherwise
"""
-
from stem.util import log
- try:
- from nacl import encoding
- from nacl import signing
- return True
- except ImportError:
- log.log_once('stem.prereq._is_pynacl_available', log.INFO, PYNACL_UNAVAILABLE)
+ if not is_crypto_available():
return False
+
+ from cryptography.hazmat.backends.openssl.backend import backend
+ supported = hasattr(backend, 'ed25519_supported') and backend.ed25519_supported()
+ if not supported:
+ log.log_once('stem.prereq._is_crypto_ed25519_supported', log.INFO, ED25519_UNSUPPORTED)
+ return supported
diff --git a/test/require.py b/test/require.py
index e6a0bbcc..1117ea1e 100644
--- a/test/require.py
+++ b/test/require.py
@@ -12,7 +12,6 @@ run.
|- needs - skips the test unless a requirement is met
|
|- cryptography - skips test unless the cryptography module is present
- |- pynacl - skips test unless the pynacl module is present
|- command - requires a command to be on the path
|- proc - requires the platform to have recognized /proc contents
|
@@ -99,7 +98,7 @@ def version(req_version):
cryptography = needs(stem.prereq.is_crypto_available, 'requires cryptography')
-pynacl = needs(stem.prereq._is_pynacl_available, 'requires pynacl module')
+ed25519_support = needs(stem.prereq._is_crypto_ed25519_supported, 'requires ed25519 support')
proc = needs(stem.util.proc.is_available, 'proc unavailable')
controller = needs(_can_access_controller, 'no connection')
ptrace = needs(_can_ptrace, 'DisableDebuggerAttachment is set')
diff --git a/test/settings.cfg b/test/settings.cfg
index d463cac9..6bdf9394 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -177,8 +177,6 @@ pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.modes'
pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.Cipher' imported but unused
pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.algorithms' imported but unused
pyflakes.ignore stem/prereq.py => 'lzma' imported but unused
-pyflakes.ignore stem/prereq.py => 'nacl.encoding' imported but unused
-pyflakes.ignore stem/prereq.py => 'nacl.signing' imported but unused
pyflakes.ignore stem/response/events.py => undefined name 'long'
pyflakes.ignore stem/util/__init__.py => undefined name 'long'
pyflakes.ignore stem/util/__init__.py => undefined name 'unicode'
diff --git a/test/task.py b/test/task.py
index c527a0c7..7b75aa47 100644
--- a/test/task.py
+++ b/test/task.py
@@ -14,7 +14,6 @@
|- PYTHON_VERSION - checks our python version
|- PLATFORM_VERSION - checks our operating system version
|- CRYPTO_VERSION - checks our version of cryptography
- |- PYNACL_VERSION - checks our version of pynacl
|- MOCK_VERSION - checks our version of mock
|- PYFLAKES_VERSION - checks our version of pyflakes
|- PYCODESTYLE_VERSION - checks our version of pycodestyle
@@ -313,7 +312,6 @@ TOR_VERSION = Task('tor version', _check_tor_version)
PYTHON_VERSION = Task('python version', _check_python_version)
PLATFORM_VERSION = Task('operating system', _check_platform_version)
CRYPTO_VERSION = ModuleVersion('cryptography version', 'cryptography', stem.prereq.is_crypto_available)
-PYNACL_VERSION = ModuleVersion('pynacl version', 'nacl', stem.prereq._is_pynacl_available)
MOCK_VERSION = ModuleVersion('mock version', ['unittest.mock', 'mock'], stem.prereq.is_mock_available)
PYFLAKES_VERSION = ModuleVersion('pyflakes version', 'pyflakes')
PYCODESTYLE_VERSION = ModuleVersion('pycodestyle version', ['pycodestyle', 'pep8'])
diff --git a/test/unit/descriptor/certificate.py b/test/unit/descriptor/certificate.py
index 28e0d20b..ca0a626e 100644
--- a/test/unit/descriptor/certificate.py
+++ b/test/unit/descriptor/certificate.py
@@ -157,7 +157,7 @@ class TestEd25519Certificate(unittest.TestCase):
exc_msg = 'Ed25519 HAS_SIGNING_KEY extension must be 32 bytes, but was 2.'
self.assertRaisesWith(ValueError, exc_msg, Ed25519Certificate.parse, certificate(extension_data = [b'\x00\x02\x04\x07\11\12']))
- @test.require.pynacl
+ @test.require.ed25519_support
def test_validation_with_descriptor_key(self):
"""
Validate a descriptor signature using the ed25519 master key within the
@@ -169,7 +169,7 @@ class TestEd25519Certificate(unittest.TestCase):
desc.certificate.validate(desc)
- @test.require.pynacl
+ @test.require.ed25519_support
def test_validation_with_embedded_key(self):
"""
Validate a descriptor signature using the signing key within the ed25519
@@ -182,7 +182,7 @@ class TestEd25519Certificate(unittest.TestCase):
desc.ed25519_master_key = None
desc.certificate.validate(desc)
- @test.require.pynacl
+ @test.require.ed25519_support
def test_validation_with_invalid_descriptor(self):
"""
Validate a descriptor without a valid signature.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits