Repository: twisted/twisted

Issue:
Update test environment to have all test pass
This looks like a `pynacl` related issue, but `1.5.0`  is used in both the initial successful run and the new failed re-run.

There were some changes in `cryptography==41.0.0` 

This run was green https://github.com/twisted/twisted/actions/runs/4921915478/jobs/8792209998

And the re-run is red - https://github.com/twisted/twisted/actions/runs/4921915478

See what option we have to get `trunk` CI green again.

so this might be due to our detection of ED25519

``
if default_backend().ed25519_supported():
```

and looking at the code, I don't know why pynacl is installed in the first place.
`couch-pynacl` should be an option dependency and act as manual fallback.


Discussion:

Resolving patch:
diff --git a/src/twisted/conch/ssh/_keys_pynacl.py b/src/twisted/conch/ssh/_keys_pynacl.py
deleted file mode 100644
--- a/src/twisted/conch/ssh/_keys_pynacl.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- test-case-name: twisted.conch.test.test_keys -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Optional PyNaCl fallback code for Ed25519 keys.
-"""
-
-from cryptography.exceptions import InvalidSignature
-from cryptography.hazmat.primitives import serialization
-from cryptography.hazmat.primitives.asymmetric import ed25519
-from nacl.exceptions import BadSignatureError
-from nacl.signing import SigningKey, VerifyKey
-
-
-class Ed25519PublicKey(ed25519.Ed25519PublicKey):
-    def __init__(self, data: bytes):
-        self._key = VerifyKey(data)
-
-    def __bytes__(self) -> bytes:
-        return bytes(self._key)
-
-    def __hash__(self) -> int:
-        return hash(bytes(self))
-
-    def __eq__(self, other: object) -> bool:
-        if not isinstance(other, self.__class__):
-            return False
-        return self._key == other._key
-
-    def __ne__(self, other: object) -> bool:
-        return not (self == other)
-
-    @classmethod
-    def from_public_bytes(cls, data: bytes) -> ed25519.Ed25519PublicKey:
-        return cls(data)
-
-    def public_bytes(
-        self,
-        encoding: serialization.Encoding,
-        format: serialization.PublicFormat,
-    ) -> bytes:
-        if (
-            encoding is not serialization.Encoding.Raw
-            or format is not serialization.PublicFormat.Raw
-        ):
-            raise ValueError("Both encoding and format must be Raw")
-        return bytes(self)
-
-    def verify(self, signature: bytes, data: bytes) -> None:
-        try:
-            self._key.verify(data, signature)
-        except BadSignatureError as e:
-            raise InvalidSignature(str(e))
-
-
-class Ed25519PrivateKey(ed25519.Ed25519PrivateKey):
-    def __init__(self, data: bytes):
-        self._key = SigningKey(data)
-
-    def __bytes__(self) -> bytes:
-        return bytes(self._key)
-
-    def __hash__(self) -> int:
-        return hash(bytes(self))
-
-    def __eq__(self, other: object) -> bool:
-        if not isinstance(other, self.__class__):
-            return False
-        return self._key == other._key
-
-    def __ne__(self, other: object) -> bool:
-        return not (self == other)
-
-    @classmethod
-    def generate(cls) -> ed25519.Ed25519PrivateKey:
-        return cls(bytes(SigningKey.generate()))
-
-    @classmethod
-    def from_private_bytes(cls, data: bytes) -> ed25519.Ed25519PrivateKey:
-        return cls(data)
-
-    def public_key(self) -> ed25519.Ed25519PublicKey:
-        return Ed25519PublicKey(bytes(self._key.verify_key))
-
-    def private_bytes(
-        self,
-        encoding: serialization.Encoding,
-        format: serialization.PrivateFormat,
-        encryption_algorithm: serialization.KeySerializationEncryption,
-    ) -> bytes:
-        if (
-            encoding is not serialization.Encoding.Raw
-            or format is not serialization.PrivateFormat.Raw
-            or not isinstance(encryption_algorithm, serialization.NoEncryption)
-        ):
-            raise ValueError(
-                "Encoding and format must be Raw and "
-                "encryption_algorithm must be NoEncryption"
-            )
-        return bytes(self)
-
-    def sign(self, data: bytes) -> bytes:
-        return self._key.sign(data).signature
diff --git a/src/twisted/conch/ssh/keys.py b/src/twisted/conch/ssh/keys.py
--- a/src/twisted/conch/ssh/keys.py
+++ b/src/twisted/conch/ssh/keys.py
@@ -13,7 +13,6 @@
 import warnings
 from base64 import b64encode, decodebytes, encodebytes
 from hashlib import md5, sha256
-from typing import Optional, Type
 
 import bcrypt
 from cryptography import utils
@@ -61,18 +60,8 @@
 }
 
 
-Ed25519PublicKey: Optional[Type[ed25519.Ed25519PublicKey]]
-Ed25519PrivateKey: Optional[Type[ed25519.Ed25519PrivateKey]]
-
-if default_backend().ed25519_supported():
-    Ed25519PublicKey = ed25519.Ed25519PublicKey
-    Ed25519PrivateKey = ed25519.Ed25519PrivateKey
-else:  # pragma: no cover
-    try:
-        from twisted.conch.ssh._keys_pynacl import Ed25519PrivateKey, Ed25519PublicKey
-    except ImportError:
-        Ed25519PublicKey = None
-        Ed25519PrivateKey = None
+Ed25519PublicKey = ed25519.Ed25519PublicKey
+Ed25519PrivateKey = ed25519.Ed25519PrivateKey
 
 
 class BadKeyError(Exception):


Test patch:
diff --git a/src/twisted/conch/test/test_keys.py b/src/twisted/conch/test/test_keys.py
--- a/src/twisted/conch/test/test_keys.py
+++ b/src/twisted/conch/test/test_keys.py
@@ -20,20 +20,15 @@
 if cryptography is None:
     skipCryptography = "Cannot run without cryptography."
 
-_keys_pynacl = requireModule("twisted.conch.ssh._keys_pynacl")
-
 
 if cryptography:
-    from cryptography.exceptions import InvalidSignature
     from cryptography.hazmat.backends import default_backend
     from cryptography.hazmat.primitives import hashes
     from cryptography.hazmat.primitives.asymmetric import padding
 
     from twisted.conch.ssh import common, keys, sexpy
 
-    ED25519_SUPPORTED = (
-        default_backend().ed25519_supported() or _keys_pynacl is not None
-    )
+    ED25519_SUPPORTED = default_backend().ed25519_supported()
 else:
     ED25519_SUPPORTED = False
 
@@ -1665,156 +1660,6 @@ def test_reprPrivateEd25519(self):
         )
 
 
-class PyNaClKeyTests(KeyTests):
-    """
-    Key tests, but forcing the use of C{PyNaCl}.
-    """
-
-    if cryptography is None:
-        skip = skipCryptography
-    if _keys_pynacl is None:
-        skip = "Cannot run without PyNaCl"
-
-    def setUp(self):
-        super().setUp()
-        self.patch(keys, "Ed25519PublicKey", _keys_pynacl.Ed25519PublicKey)
-        self.patch(keys, "Ed25519PrivateKey", _keys_pynacl.Ed25519PrivateKey)
-
-    def test_naclPrivateBytes(self):
-        """
-        L{_keys_pynacl.Ed25519PrivateKey.private_bytes} and
-        L{_keys_pynacl.Ed25519PrivateKey.from_private_bytes} round-trip.
-        """
-        from cryptography.hazmat.primitives import serialization
-
-        key = _keys_pynacl.Ed25519PrivateKey.generate()
-        key_bytes = key.private_bytes(
-            serialization.Encoding.Raw,
-            serialization.PrivateFormat.Raw,
-            serialization.NoEncryption(),
-        )
-        self.assertIsInstance(key_bytes, bytes)
-        self.assertEqual(
-            key, _keys_pynacl.Ed25519PrivateKey.from_private_bytes(key_bytes)
-        )
-
-    def test_naclPrivateBytesInvalidParameters(self):
-        """
-        L{_keys_pynacl.Ed25519PrivateKey.private_bytes} only accepts certain parameters.
-        """
-        from cryptography.hazmat.primitives import serialization
-
-        key = _keys_pynacl.Ed25519PrivateKey.generate()
-        self.assertRaises(
-            ValueError,
-            key.private_bytes,
-            serialization.Encoding.PEM,
-            serialization.PrivateFormat.Raw,
-            serialization.NoEncryption(),
-        )
-        self.assertRaises(
-            ValueError,
-            key.private_bytes,
-            serialization.Encoding.Raw,
-            serialization.PrivateFormat.PKCS8,
-            serialization.NoEncryption(),
-        )
-        self.assertRaises(
-            ValueError,
-            key.private_bytes,
-            serialization.Encoding.Raw,
-            serialization.PrivateFormat.Raw,
-            serialization.BestAvailableEncryption(b"password"),
-        )
-
-    def test_naclPrivateHash(self):
-        """
-        L{_keys_pynacl.Ed25519PrivateKey.__hash__} allows instances to be hashed.
-        """
-        key = _keys_pynacl.Ed25519PrivateKey.generate()
-        d = {key: True}
-        self.assertTrue(d[key])
-
-    def test_naclPrivateEquality(self):
-        """
-        L{_keys_pynacl.Ed25519PrivateKey} implements equality test methods.
-        """
-        key1 = _keys_pynacl.Ed25519PrivateKey.generate()
-        key2 = _keys_pynacl.Ed25519PrivateKey.generate()
-        self.assertEqual(key1, key1)
-        self.assertNotEqual(key1, key2)
-        self.assertNotEqual(key1, bytes(key1))
-
-    def test_naclPublicBytes(self):
-        """
-        L{_keys_pynacl.Ed25519PublicKey.public_bytes} and
-        L{_keys_pynacl.Ed25519PublicKey.from_public_bytes} round-trip.
-        """
-        from cryptography.hazmat.primitives import serialization
-
-        key = _keys_pynacl.Ed25519PrivateKey.generate().public_key()
-        key_bytes = key.public_bytes(
-            serialization.Encoding.Raw, serialization.PublicFormat.Raw
-        )
-        self.assertIsInstance(key_bytes, bytes)
-        self.assertEqual(
-            key, _keys_pynacl.Ed25519PublicKey.from_public_bytes(key_bytes)
-        )
-
-    def test_naclPublicBytesInvalidParameters(self):
-        """
-        L{_keys_pynacl.Ed25519PublicKey.public_bytes} only accepts certain parameters.
-        """
-        from cryptography.hazmat.primitives import serialization
-
-        key = _keys_pynacl.Ed25519PrivateKey.generate().public_key()
-        self.assertRaises(
-            ValueError,
-            key.public_bytes,
-            serialization.Encoding.PEM,
-            serialization.PublicFormat.Raw,
-        )
-        self.assertRaises(
-            ValueError,
-            key.public_bytes,
-            serialization.Encoding.Raw,
-            serialization.PublicFormat.PKCS1,
-        )
-
-    def test_naclPublicHash(self):
-        """
-        L{_keys_pynacl.Ed25519PublicKey.__hash__} allows instances to be hashed.
-        """
-        key = _keys_pynacl.Ed25519PrivateKey.generate().public_key()
-        d = {key: True}
-        self.assertTrue(d[key])
-
-    def test_naclPublicEquality(self):
-        """
-        L{_keys_pynacl.Ed25519PublicKey} implements equality test methods.
-        """
-        key1 = _keys_pynacl.Ed25519PrivateKey.generate().public_key()
-        key2 = _keys_pynacl.Ed25519PrivateKey.generate().public_key()
-        self.assertEqual(key1, key1)
-        self.assertNotEqual(key1, key2)
-        self.assertNotEqual(key1, bytes(key1))
-
-    def test_naclVerify(self):
-        """
-        L{_keys_pynacl.Ed25519PublicKey.verify} raises appropriate exceptions.
-        """
-        key = _keys_pynacl.Ed25519PrivateKey.generate()
-        self.assertIsInstance(key, keys.Ed25519PrivateKey)
-        signature = key.sign(b"test data")
-        self.assertIsNone(key.public_key().verify(signature, b"test data"))
-        self.assertRaises(
-            InvalidSignature, key.public_key().verify, signature, b"wrong data"
-        )
-        self.assertRaises(
-            InvalidSignature, key.public_key().verify, b"0" * 64, b"test data"
-        )
-
-
 class PersistentRSAKeyTests(unittest.TestCase):
     """
     Tests for L{keys._getPersistentRSAKey}.