Repository: pyca/cryptography

Issue:
{RSA,DSA,Elliptic}{Private,Public}Numbers should provide __eq__ and __ne__ implementations
And these can compare the numbers by values.



Discussion:

Resolving patch:
diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py
--- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py
@@ -59,6 +59,15 @@ def __init__(self, p, q, g):
     def parameters(self, backend):
         return backend.load_dsa_parameter_numbers(self)
 
+    def __eq__(self, other):
+        if not isinstance(other, DSAParameterNumbers):
+            return NotImplemented
+
+        return self.p == other.p and self.q == other.q and self.g == other.g
+
+    def __ne__(self, other):
+        return not self == other
+
 
 class DSAPublicNumbers(object):
     def __init__(self, y, parameter_numbers):
@@ -79,6 +88,18 @@ def __init__(self, y, parameter_numbers):
     def public_key(self, backend):
         return backend.load_dsa_public_numbers(self)
 
+    def __eq__(self, other):
+        if not isinstance(other, DSAPublicNumbers):
+            return NotImplemented
+
+        return (
+            self.y == other.y and
+            self.parameter_numbers == other.parameter_numbers
+        )
+
+    def __ne__(self, other):
+        return not self == other
+
 
 class DSAPrivateNumbers(object):
     def __init__(self, x, public_numbers):
@@ -97,3 +118,14 @@ def __init__(self, x, public_numbers):
 
     def private_key(self, backend):
         return backend.load_dsa_private_numbers(self)
+
+    def __eq__(self, other):
+        if not isinstance(other, DSAPrivateNumbers):
+            return NotImplemented
+
+        return (
+            self.x == other.x and self.public_numbers == other.public_numbers
+        )
+
+    def __ne__(self, other):
+        return not self == other
diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py
--- a/src/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -161,6 +161,20 @@ def public_key(self, backend):
     x = utils.read_only_property("_x")
     y = utils.read_only_property("_y")
 
+    def __eq__(self, other):
+        if not isinstance(other, EllipticCurvePublicNumbers):
+            return NotImplemented
+
+        return (
+            self.x == other.x and
+            self.y == other.y and
+            self.curve.name == other.curve.name and
+            self.curve.key_size == other.curve.key_size
+        )
+
+    def __ne__(self, other):
+        return not self == other
+
 
 class EllipticCurvePrivateNumbers(object):
     def __init__(self, private_value, public_numbers):
@@ -184,3 +198,15 @@ def private_key(self, backend):
 
     private_value = utils.read_only_property("_private_value")
     public_numbers = utils.read_only_property("_public_numbers")
+
+    def __eq__(self, other):
+        if not isinstance(other, EllipticCurvePrivateNumbers):
+            return NotImplemented
+
+        return (
+            self.private_value == other.private_value and
+            self.public_numbers == other.public_numbers
+        )
+
+    def __ne__(self, other):
+        return not self == other
diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
--- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -160,6 +160,23 @@ def __init__(self, p, q, d, dmp1, dmq1, iqmp,
     def private_key(self, backend):
         return backend.load_rsa_private_numbers(self)
 
+    def __eq__(self, other):
+        if not isinstance(other, RSAPrivateNumbers):
+            return NotImplemented
+
+        return (
+            self.p == other.p and
+            self.q == other.q and
+            self.d == other.d and
+            self.dmp1 == other.dmp1 and
+            self.dmq1 == other.dmq1 and
+            self.iqmp == other.iqmp and
+            self.public_numbers == other.public_numbers
+        )
+
+    def __ne__(self, other):
+        return not self == other
+
 
 class RSAPublicNumbers(object):
     def __init__(self, e, n):
@@ -180,3 +197,12 @@ def public_key(self, backend):
 
     def __repr__(self):
         return "<RSAPublicNumbers(e={0}, n={1})>".format(self._e, self._n)
+
+    def __eq__(self, other):
+        if not isinstance(other, RSAPublicNumbers):
+            return NotImplemented
+
+        return self.e == other.e and self.n == other.n
+
+    def __ne__(self, other):
+        return not self == other


Test patch:
diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py
--- a/tests/hazmat/primitives/test_dsa.py
+++ b/tests/hazmat/primitives/test_dsa.py
@@ -705,3 +705,67 @@ def test_dsa_private_numbers_invalid_types(self):
 
         with pytest.raises(TypeError):
             dsa.DSAPrivateNumbers(x=None, public_numbers=public_numbers)
+
+
+class TestDSANumberEquality(object):
+    def test_parameter_numbers_eq(self):
+        param = dsa.DSAParameterNumbers(1, 2, 3)
+        assert param == dsa.DSAParameterNumbers(1, 2, 3)
+
+    def test_parameter_numbers_ne(self):
+        param = dsa.DSAParameterNumbers(1, 2, 3)
+        assert param != dsa.DSAParameterNumbers(1, 2, 4)
+        assert param != dsa.DSAParameterNumbers(1, 1, 3)
+        assert param != dsa.DSAParameterNumbers(2, 2, 3)
+        assert param != object()
+
+    def test_public_numbers_eq(self):
+        pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3))
+        assert pub == dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3))
+
+    def test_public_numbers_ne(self):
+        pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3))
+        assert pub != dsa.DSAPublicNumbers(2, dsa.DSAParameterNumbers(1, 2, 3))
+        assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(2, 2, 3))
+        assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 3, 3))
+        assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 4))
+        assert pub != object()
+
+    def test_private_numbers_eq(self):
+        pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3))
+        priv = dsa.DSAPrivateNumbers(1, pub)
+        assert priv == dsa.DSAPrivateNumbers(
+            1, dsa.DSAPublicNumbers(
+                1, dsa.DSAParameterNumbers(1, 2, 3)
+            )
+        )
+
+    def test_private_numbers_ne(self):
+        pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3))
+        priv = dsa.DSAPrivateNumbers(1, pub)
+        assert priv != dsa.DSAPrivateNumbers(
+            2, dsa.DSAPublicNumbers(
+                1, dsa.DSAParameterNumbers(1, 2, 3)
+            )
+        )
+        assert priv != dsa.DSAPrivateNumbers(
+            1, dsa.DSAPublicNumbers(
+                2, dsa.DSAParameterNumbers(1, 2, 3)
+            )
+        )
+        assert priv != dsa.DSAPrivateNumbers(
+            1, dsa.DSAPublicNumbers(
+                1, dsa.DSAParameterNumbers(2, 2, 3)
+            )
+        )
+        assert priv != dsa.DSAPrivateNumbers(
+            1, dsa.DSAPublicNumbers(
+                1, dsa.DSAParameterNumbers(1, 3, 3)
+            )
+        )
+        assert priv != dsa.DSAPrivateNumbers(
+            1, dsa.DSAPublicNumbers(
+                1, dsa.DSAParameterNumbers(1, 2, 4)
+            )
+        )
+        assert priv != object()
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -360,3 +360,40 @@ def test_deprecated_public_private_key_load(self):
         numbers = ec.EllipticCurvePrivateNumbers(1, pub_numbers)
         assert numbers.private_key(b) == b"private_key"
         assert pub_numbers.public_key(b) == b"public_key"
+
+
+class TestECNumbersEquality(object):
+    def test_public_numbers_eq(self):
+        pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+        assert pub == ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+
+    def test_public_numbers_ne(self):
+        pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+        assert pub != ec.EllipticCurvePublicNumbers(1, 2, ec.SECP384R1())
+        assert pub != ec.EllipticCurvePublicNumbers(1, 3, ec.SECP192R1())
+        assert pub != ec.EllipticCurvePublicNumbers(2, 2, ec.SECP192R1())
+        assert pub != object()
+
+    def test_private_numbers_eq(self):
+        pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+        priv = ec.EllipticCurvePrivateNumbers(1, pub)
+        assert priv == ec.EllipticCurvePrivateNumbers(
+            1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+        )
+
+    def test_private_numbers_ne(self):
+        pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+        priv = ec.EllipticCurvePrivateNumbers(1, pub)
+        assert priv != ec.EllipticCurvePrivateNumbers(
+            2, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1())
+        )
+        assert priv != ec.EllipticCurvePrivateNumbers(
+            1, ec.EllipticCurvePublicNumbers(2, 2, ec.SECP192R1())
+        )
+        assert priv != ec.EllipticCurvePrivateNumbers(
+            1, ec.EllipticCurvePublicNumbers(1, 3, ec.SECP192R1())
+        )
+        assert priv != ec.EllipticCurvePrivateNumbers(
+            1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP521R1())
+        )
+        assert priv != object()
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -18,7 +18,9 @@
 from cryptography.hazmat.backends.interfaces import RSABackend
 from cryptography.hazmat.primitives import hashes, interfaces
 from cryptography.hazmat.primitives.asymmetric import padding, rsa
-from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
+from cryptography.hazmat.primitives.asymmetric.rsa import (
+    RSAPrivateNumbers, RSAPublicNumbers
+)
 
 from .fixtures_rsa import (
     RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028,
@@ -1647,3 +1649,52 @@ def test_invalid_private_numbers_argument_values(self, backend):
     def test_public_number_repr(self):
         num = RSAPublicNumbers(1, 1)
         assert repr(num) == "<RSAPublicNumbers(e=1, n=1)>"
+
+
+class TestRSANumbersEquality(object):
+    def test_public_numbers_eq(self):
+        num = RSAPublicNumbers(1, 2)
+        num2 = RSAPublicNumbers(1, 2)
+        assert num == num2
+
+    def test_public_numbers_ne(self):
+        num = RSAPublicNumbers(1, 2)
+        assert num != RSAPublicNumbers(2, 2)
+        assert num != RSAPublicNumbers(1, 3)
+        assert num != object()
+
+    def test_private_numbers_eq(self):
+        pub = RSAPublicNumbers(1, 2)
+        num = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub)
+        pub2 = RSAPublicNumbers(1, 2)
+        num2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub2)
+        assert num == num2
+
+    def test_private_numbers_ne(self):
+        pub = RSAPublicNumbers(1, 2)
+        num = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub)
+        assert num != RSAPrivateNumbers(
+            1, 2, 3, 4, 5, 7, RSAPublicNumbers(1, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            1, 2, 3, 4, 4, 6, RSAPublicNumbers(1, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            1, 2, 3, 5, 5, 6, RSAPublicNumbers(1, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            1, 2, 4, 4, 5, 6, RSAPublicNumbers(1, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            1, 3, 3, 4, 5, 6, RSAPublicNumbers(1, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            2, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            1, 2, 3, 4, 5, 6, RSAPublicNumbers(2, 2)
+        )
+        assert num != RSAPrivateNumbers(
+            1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3)
+        )
+        assert num != object()