Repository: pyca/cryptography

Issue:
PrecertPoison and OCSPNoCheck do not compare as equal
Given the same input parameters, two extension instances usually compare as equal:

```
>>> x509.OCSPNonce(b'foo') == x509.OCSPNonce(b'foo')
True
>>> x509.SubjectKeyIdentifier(b'333333') == x509.SubjectKeyIdentifier(b'333333')
True
```

However this does not seem true for PrecertPoison and OCSPNoCheck:

```
>>> x509.PrecertPoison() == x509.PrecertPoison()
False
>>> x509.OCSPNoCheck() == x509.OCSPNoCheck()
False
```

Or am I holding this wrong?


Discussion:
Those two extensions don't implement `__eq__`. They're empty extensions with no value so I wouldn't have thought comparison would be particularly useful, but that doesn't make me right. 😄 

However, if we implement `__eq__` we'll make the class unhashable so we also need to implement `__hash__`. And we'll need to implement `__ne__` if we're doing `__eq__` and we might as well have `__repr__` just for consistency with the other extension classes. I'd be happy to review a PR that implements those 4 methods for the two classes plus associated tests. We have lots of examples of testing for those methods in our `test_x509_ext.py`.

Resolving patch:
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -856,11 +856,35 @@ def __hash__(self):
 class OCSPNoCheck(object):
     oid = ExtensionOID.OCSP_NO_CHECK
 
+    def __eq__(self, other):
+        if not isinstance(other, OCSPNoCheck):
+            return NotImplemented
+
+        return True
+
+    def __hash__(self):
+        return hash(OCSPNoCheck)
+
+    def __repr__(self):
+        return "<OCSPNoCheck()>"
+
 
 @utils.register_interface(ExtensionType)
 class PrecertPoison(object):
     oid = ExtensionOID.PRECERT_POISON
 
+    def __eq__(self, other):
+        if not isinstance(other, PrecertPoison):
+            return NotImplemented
+
+        return True
+
+    def __hash__(self):
+        return hash(PrecertPoison)
+
+    def __repr__(self):
+        return "<PrecertPoison()>"
+
 
 @utils.register_interface(ExtensionType)
 class TLSFeature(object):


Test patch:
diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py
--- a/tests/x509/test_x509_ext.py
+++ b/tests/x509/test_x509_ext.py
@@ -4391,6 +4391,28 @@ def test_nocheck(self, backend):
         )
         assert isinstance(ext.value, x509.OCSPNoCheck)
 
+    def test_eq(self):
+        onc1 = x509.OCSPNoCheck()
+        onc2 = x509.OCSPNoCheck()
+
+        assert onc1 == onc2
+
+    def test_hash(self):
+        onc1 = x509.OCSPNoCheck()
+        onc2 = x509.OCSPNoCheck()
+
+        assert hash(onc1) == hash(onc2)
+
+    def test_ne(self):
+        onc = x509.OCSPNoCheck()
+
+        assert onc != object()
+
+    def test_repr(self):
+        onc = x509.OCSPNoCheck()
+
+        assert repr(onc) == '<OCSPNoCheck()>'
+
 
 class TestInhibitAnyPolicy(object):
     def test_not_int(self):
@@ -4894,6 +4916,28 @@ def test_generate(self, backend):
         ).value
         assert isinstance(poison, x509.PrecertPoison)
 
+    def test_eq(self):
+        pcp1 = x509.PrecertPoison()
+        pcp2 = x509.PrecertPoison()
+
+        assert pcp1 == pcp2
+
+    def test_hash(self):
+        pcp1 = x509.PrecertPoison()
+        pcp2 = x509.PrecertPoison()
+
+        assert hash(pcp1) == hash(pcp2)
+
+    def test_ne(self):
+        pcp = x509.PrecertPoison()
+
+        assert pcp != object()
+
+    def test_repr(self):
+        pcp = x509.PrecertPoison()
+
+        assert repr(pcp) == '<PrecertPoison()>'
+
 
 @pytest.mark.requires_backend_interface(interface=RSABackend)
 @pytest.mark.requires_backend_interface(interface=X509Backend)