diff --git a/old_openssl.patch b/old_openssl.patch new file mode 100644 index 0000000..00871fd --- /dev/null +++ b/old_openssl.patch @@ -0,0 +1,379 @@ +From 1dee113bb3e4a6888de562b0413e9abd6a0f0f04 Mon Sep 17 00:00:00 2001 +From: Ron Frederick +Date: Fri, 19 Apr 2019 16:19:43 -0700 +Subject: [PATCH] Restore libnacl support for curve25519/ed25519 as a fallback + for PyCA + +This commit restores the ability to use libnacl/libsodium for +curve25519/ed25519 when the version of OpenSSL available through +the cryptography package does not have this support. + +This commit also fixes a bug where ed25519 and ed448 were being +registered as valid host key algorithms even when support for them was +not available on the system. +--- + asyncssh/crypto/ed.py | 273 +++++++++++++++++++++++++++++++----------- + asyncssh/eddsa.py | 16 ++- + docs/api.rst | 10 +- + 3 files changed, 219 insertions(+), 80 deletions(-) + +Index: asyncssh-1.16.1/asyncssh/crypto/ed.py +=================================================================== +--- asyncssh-1.16.1.orig/asyncssh/crypto/ed.py ++++ asyncssh-1.16.1/asyncssh/crypto/ed.py +@@ -18,7 +18,10 @@ + # Contributors: + # Ron Frederick - initial implementation, API, and documentation + +-"""A shim around PyCA for Edwards-curve keys and key exchange""" ++"""A shim around PyCA and libnacl for Edwards-curve keys and key exchange""" ++ ++import ctypes ++import os + + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.backends.openssl import backend +@@ -38,112 +41,240 @@ curve25519_available = backend.x25519_su + curve448_available = backend.x448_supported() + + +-class _EdKey(PyCAKey): +- """Base class for shim around PyCA for Ed25519/Ed448 keys""" ++if ed25519_available: # pragma: no branch ++ class _EdKey(PyCAKey): ++ """Base class for shim around PyCA for Ed25519/Ed448 keys""" + +- def __init__(self, pyca_key, pub, priv=None): +- super().__init__(pyca_key) ++ def __init__(self, pyca_key, pub, priv=None): ++ super().__init__(pyca_key) + +- self._pub = pub +- self._priv = priv ++ self._pub = pub ++ self._priv = priv + +- @property +- def public_value(self): +- """Return the public value encoded as a byte string""" ++ @property ++ def public_value(self): ++ """Return the public value encoded as a byte string""" + +- return self._pub ++ return self._pub + +- @property +- def private_value(self): +- """Return the private value encoded as a byte string""" ++ @property ++ def private_value(self): ++ """Return the private value encoded as a byte string""" + +- return self._priv ++ return self._priv + + +-class EdDSAPrivateKey(_EdKey): +- """A shim around PyCA for EdDSA private keys""" ++ class EdDSAPrivateKey(_EdKey): ++ """A shim around PyCA for EdDSA private keys""" + +- _priv_classes = {b'ed25519': ed25519.Ed25519PrivateKey, +- b'ed448': ed448.Ed448PrivateKey} ++ _priv_classes = {b'ed25519': ed25519.Ed25519PrivateKey, ++ b'ed448': ed448.Ed448PrivateKey} + +- @classmethod +- def construct(cls, curve_id, priv): +- """Construct an EdDSA private key""" ++ @classmethod ++ def construct(cls, curve_id, priv): ++ """Construct an EdDSA private key""" + +- priv_cls = cls._priv_classes[curve_id] +- priv_key = priv_cls.from_private_bytes(priv) +- pub_key = priv_key.public_key() +- pub = pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw) ++ priv_cls = cls._priv_classes[curve_id] ++ priv_key = priv_cls.from_private_bytes(priv) ++ pub_key = priv_key.public_key() ++ pub = pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw) + +- return cls(priv_key, pub, priv) ++ return cls(priv_key, pub, priv) + +- @classmethod +- def generate(cls, curve_id): +- """Generate a new ECDSA private key""" ++ @classmethod ++ def generate(cls, curve_id): ++ """Generate a new EdDSA private key""" + +- priv_cls = cls._priv_classes[curve_id] +- priv_key = priv_cls.generate() +- priv = priv_key.private_bytes(Encoding.Raw, PrivateFormat.Raw, +- NoEncryption()) ++ priv_cls = cls._priv_classes[curve_id] ++ priv_key = priv_cls.generate() ++ priv = priv_key.private_bytes(Encoding.Raw, PrivateFormat.Raw, ++ NoEncryption()) + +- pub_key = priv_key.public_key() +- pub = pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw) ++ pub_key = priv_key.public_key() ++ pub = pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw) + +- return cls(priv_key, pub, priv) ++ return cls(priv_key, pub, priv) + +- def sign(self, data): +- """Sign a block of data""" ++ def sign(self, data): ++ """Sign a block of data""" + +- return self.pyca_key.sign(data) ++ return self.pyca_key.sign(data) + + +-class EdDSAPublicKey(_EdKey): +- """A shim around PyCA for EdDSA public keys""" ++ class EdDSAPublicKey(_EdKey): ++ """A shim around PyCA for EdDSA public keys""" + +- _pub_classes = {b'ed25519': ed25519.Ed25519PublicKey, +- b'ed448': ed448.Ed448PublicKey} ++ _pub_classes = {b'ed25519': ed25519.Ed25519PublicKey, ++ b'ed448': ed448.Ed448PublicKey} + +- @classmethod +- def construct(cls, curve_id, pub): +- """Construct an ECDSA public key""" ++ @classmethod ++ def construct(cls, curve_id, pub): ++ """Construct an EdDSA public key""" + +- pub_cls = cls._pub_classes[curve_id] +- pub_key = pub_cls.from_public_bytes(pub) ++ pub_cls = cls._pub_classes[curve_id] ++ pub_key = pub_cls.from_public_bytes(pub) + +- return cls(pub_key, pub) ++ return cls(pub_key, pub) + +- def verify(self, data, sig): +- """Verify the signature on a block of data""" ++ def verify(self, data, sig): ++ """Verify the signature on a block of data""" + +- try: +- self.pyca_key.verify(sig, data) +- return True +- except InvalidSignature: +- return False ++ try: ++ self.pyca_key.verify(sig, data) ++ return True ++ except InvalidSignature: ++ return False ++else: # pragma: no cover ++ class _EdKey: ++ """Base class for shim around libnacl for Ed25519 keys""" + ++ def __init__(self, pub, priv=None): ++ self._pub = pub ++ self._priv = priv + +-class Curve25519DH: +- """Curve25519 Diffie Hellman implementation""" ++ @property ++ def public_value(self): ++ """Return the public value encoded as a byte string""" + +- def __init__(self): +- self._priv_key = x25519.X25519PrivateKey.generate() ++ return self._pub ++ ++ @property ++ def private_value(self): ++ """Return the private value encoded as a byte string""" ++ ++ return self._priv[:-len(self._pub)] if self._priv else None ++ ++ ++ class EdDSAPrivateKey(_EdKey): ++ """A shim around libnacl for Ed25519 private keys""" ++ ++ @classmethod ++ def construct(cls, curve_id, priv): ++ """Construct an EdDSA private key""" ++ ++ # pylint: disable=unused-argument ++ ++ return cls(*_ed25519_construct_keypair(priv)) ++ ++ @classmethod ++ def generate(cls, curve_id): ++ """Generate a new EdDSA private key""" ++ ++ # pylint: disable=unused-argument ++ ++ return cls(*_ed25519_generate_keypair()) ++ ++ def sign(self, data): ++ """Sign a block of data""" ++ ++ return _ed25519_sign(data, self._priv)[:-len(data)] ++ ++ ++ class EdDSAPublicKey(_EdKey): ++ """A shim around libnacl for Ed25519 public keys""" ++ ++ @classmethod ++ def construct(cls, curve_id, pub): ++ """Construct an EdDSA public key""" ++ ++ # pylint: disable=unused-argument ++ ++ if len(pub) != _ED25519_PUBLIC_BYTES: ++ raise ValueError('Invalid Ed25519 public key') ++ ++ return cls(pub) ++ ++ def verify(self, data, sig): ++ """Verify the signature on a block of data""" ++ ++ try: ++ return _ed25519_verify(sig + data, self._pub) == data ++ except ValueError: ++ return False ++ ++ try: ++ import libnacl ++ ++ _ED25519_PUBLIC_BYTES = libnacl.crypto_sign_ed25519_PUBLICKEYBYTES ++ ++ _ed25519_construct_keypair = libnacl.crypto_sign_seed_keypair ++ _ed25519_generate_keypair = libnacl.crypto_sign_keypair ++ _ed25519_sign = libnacl.crypto_sign ++ _ed25519_verify = libnacl.crypto_sign_open ++ ++ ed25519_available = True ++ except (ImportError, OSError, AttributeError): ++ pass ++ ++ ++if curve25519_available: # pragma: no branch ++ class Curve25519DH: ++ """Curve25519 Diffie Hellman implementation based on PyCA""" ++ ++ def __init__(self): ++ self._priv_key = x25519.X25519PrivateKey.generate() ++ ++ def get_public(self): ++ """Return the public key to send in the handshake""" ++ ++ return self._priv_key.public_key().public_bytes(Encoding.Raw, ++ PublicFormat.Raw) ++ ++ def get_shared(self, peer_public): ++ """Return the shared key from the peer's public key""" ++ ++ peer_key = x25519.X25519PublicKey.from_public_bytes(peer_public) ++ shared = self._priv_key.exchange(peer_key) ++ return int.from_bytes(shared, 'big') ++else: # pragma: no cover ++ class Curve25519DH: ++ """Curve25519 Diffie Hellman implementation based on libnacl""" ++ ++ def __init__(self): ++ self._private = os.urandom(_CURVE25519_SCALARBYTES) ++ ++ def get_public(self): ++ """Return the public key to send in the handshake""" ++ ++ public = ctypes.create_string_buffer(_CURVE25519_BYTES) ++ ++ if _curve25519_base(public, self._private) != 0: ++ # This error is never returned by libsodium ++ raise ValueError('Curve25519 failed') # pragma: no cover ++ ++ return public.raw ++ ++ def get_shared(self, peer_public): ++ """Return the shared key from the peer's public key""" ++ ++ if len(peer_public) != _CURVE25519_BYTES: ++ raise ValueError('Invalid curve25519 public key size') ++ ++ shared = ctypes.create_string_buffer(_CURVE25519_BYTES) ++ ++ if _curve25519(shared, self._private, peer_public) != 0: ++ # This error is never returned by libsodium ++ raise ValueError('Curve25519 failed') # pragma: no cover ++ ++ return int.from_bytes(shared.raw, 'big') + +- def get_public(self): +- """Return the public key to send in the handshake""" ++ try: ++ from libnacl import nacl + +- return self._priv_key.public_key().public_bytes(Encoding.Raw, +- PublicFormat.Raw) ++ _CURVE25519_BYTES = nacl.crypto_scalarmult_curve25519_bytes() ++ _CURVE25519_SCALARBYTES = \ ++ nacl.crypto_scalarmult_curve25519_scalarbytes() + +- def get_shared(self, peer_public): +- """Return the shared key from the peer's public key""" ++ _curve25519 = nacl.crypto_scalarmult_curve25519 ++ _curve25519_base = nacl.crypto_scalarmult_curve25519_base + +- peer_key = x25519.X25519PublicKey.from_public_bytes(peer_public) +- shared = self._priv_key.exchange(peer_key) +- return int.from_bytes(shared, 'big') ++ curve25519_available = True ++ except (ImportError, OSError, AttributeError): ++ pass + + + class Curve448DH: +- """Curve448 Diffie Hellman implementation""" ++ """Curve448 Diffie Hellman implementation based on PyCA""" + + def __init__(self): + self._priv_key = x448.X448PrivateKey.generate() +Index: asyncssh-1.16.1/asyncssh/eddsa.py +=================================================================== +--- asyncssh-1.16.1.orig/asyncssh/eddsa.py ++++ asyncssh-1.16.1/asyncssh/eddsa.py +@@ -22,6 +22,7 @@ + + from .asn1 import ASN1DecodeError, ObjectIdentifier, der_encode, der_decode + from .crypto import EdDSAPrivateKey, EdDSAPublicKey ++from .crypto import ed25519_available, ed448_available + from .packet import String + from .public_key import OMIT, SSHKey, SSHOpenSSHCertificateV01 + from .public_key import KeyImportError, KeyExportError +@@ -186,10 +187,15 @@ class _Ed448Key(_EdKey): + all_sig_algorithms = set(sig_algorithms) + + +-for _cls in (_Ed25519Key, _Ed448Key): +- _cert_algorithm = _cls.algorithm + b'-cert-v01@openssh.com' ++if ed25519_available: # pragma: no branch ++ register_public_key_alg(b'ssh-ed25519', _Ed25519Key) + +- register_public_key_alg(_cls.algorithm, _cls) ++ register_certificate_alg(1, b'ssh-ed25519', ++ b'ssh-ed25519-cert-v01@openssh.com', ++ _Ed25519Key, SSHOpenSSHCertificateV01) + +- register_certificate_alg(1, _cls.algorithm, _cert_algorithm, +- _cls, SSHOpenSSHCertificateV01) ++if ed448_available: # pragma: no branch ++ register_public_key_alg(b'ssh-ed448', _Ed448Key) ++ ++ register_certificate_alg(1, b'ssh-ed448', b'ssh-ed448-cert-v01@openssh.com', ++ _Ed448Key, SSHOpenSSHCertificateV01) diff --git a/python-asyncssh.changes b/python-asyncssh.changes index c889afe..6ceea11 100644 --- a/python-asyncssh.changes +++ b/python-asyncssh.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Apr 23 08:29:31 UTC 2019 - Ondřej Súkup + +- add old_openssl.patch - return support for ed25519/448 via libnacl + on systems with older openSSL + ------------------------------------------------------------------- Mon Apr 1 13:23:08 UTC 2019 - Ondřej Súkup diff --git a/python-asyncssh.spec b/python-asyncssh.spec index 1e07f8e..222ac21 100644 --- a/python-asyncssh.spec +++ b/python-asyncssh.spec @@ -26,6 +26,7 @@ License: EPL-2.0 OR GPL-2.0-or-later Group: Development/Languages/Python Url: http://asyncssh.timeheart.net Source: https://files.pythonhosted.org/packages/source/a/asyncssh/asyncssh-%{version}.tar.gz +Patch0: old_openssl.patch BuildRequires: %{python_module bcrypt >= 3.1.3} BuildRequires: %{python_module cryptography >= 2.6.1} BuildRequires: %{python_module gssapi >= 1.2.0} @@ -52,6 +53,7 @@ asyncio framework. %prep %setup -q -n asyncssh-%{version} +%patch0 -p1 %build %python_build