Repository: pyca/cryptography

Issue:
missing OCSP extensions
From https://www.rfc-editor.org/rfc/rfc6960#section-4.4:

Adobe reader uses this extension who is not supported:
`<UnrecognizedExtension(oid=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.1.4, name=Unknown OID)>, value=b'0\x0b\x06\t+\x06\x01\x05\x05\x070\x01\x01')>`

List of missing OID in cryptography/hazmat/_oid.py OCSPExtensionOID, corresponding extensions are also missing in cryptography/x509/extensions.py:
```
CRL =  ObjectIdentifier("1.3.6.1.5.5.7.48.1.3")
RESPONSE =  ObjectIdentifier("1.3.6.1.5.5.7.48.1.4")
ARCHIVE_CUTOFF =  ObjectIdentifier("1.3.6.1.5.5.7.48.1.6")
SERVICE_LOCATOR =  ObjectIdentifier("1.3.6.1.5.5.7.48.1.7")
PREF_SIG_ALGS =  ObjectIdentifier("1.3.6.1.5.5.7.48.1.8")
EXTENDED_REVOKE =  ObjectIdentifier("1.3.6.1.5.5.7.48.1.9")
```


Discussion:
btw OCSPRequest sent by Adobe reader contains DEFAULT version who should not be in DER, I need to remove it manually  before using load_der_ocsp_request.
Can you give us a sample of a DER request made by Reader so we can compare their extension structure to the RFC as well?

And does the latest Adobe Reader still generate invalid DER with the default value? If so, it should be reported to Adobe as a bug.
```
308199308196a003020100304e304c304a300906052b0e03021a050004145a23ba7c7f4608358d24cbf3292de26ccf070bb70414225e49e4aad88fe06d634b8013b15ae12fbe5920021100e5249fdaa8b47c86e7ccb85ddcf0162fa23f303d301a06092b0601050507300104040d300b06092b0601050507300101301f06092b060105050730010204120410132b615fd5ee1aeb802a176529bfdac7

SEQUENCE (1 elem)
  SEQUENCE (3 elem)
    [0] (1 elem)
      INTEGER 0
    SEQUENCE (1 elem)
      SEQUENCE (1 elem)
        SEQUENCE (4 elem)
          SEQUENCE (2 elem)
            OBJECT IDENTIFIER 1.3.14.3.2.26 sha1 (OIW)
            NULL
          OCTET STRING (20 byte) 5A23BA7C7F4608358D24CBF3292DE26CCF070BB7
          OCTET STRING (20 byte) 225E49E4AAD88FE06D634B8013B15AE12FBE5920
          INTEGER (128 bit) 304583375948731073774349032019171743279
    [2] (1 elem)
      SEQUENCE (2 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 1.3.6.1.5.5.7.48.1.4 ocspResponse (OCSP)
          OCTET STRING (13 byte) 300B06092B0601050507300101
            SEQUENCE (1 elem)
              OBJECT IDENTIFIER 1.3.6.1.5.5.7.48.1.1 ocspBasic (OCSP)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 1.3.6.1.5.5.7.48.1.2 ocspNonce (OCSP)
          OCTET STRING (18 byte) 0410132B615FD5EE1AEB802A176529BFDAC7
            OCTET STRING (16 byte) 132B615FD5EE1AEB802A176529BFDAC7
```
Is the request here that we parse this extension, instead of just handling it as unrecognized and provide the raw DER?

Do you have a link to a reference for ASN.1 structure of the extension?

Resolving patch:
diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py
--- a/src/cryptography/hazmat/_oid.py
+++ b/src/cryptography/hazmat/_oid.py
@@ -42,6 +42,7 @@ class ExtensionOID:
 
 class OCSPExtensionOID:
     NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2")
+    ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4")
 
 
 class CRLEntryExtensionOID:
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -54,6 +54,7 @@
     KeyUsage,
     NameConstraints,
     NoticeReference,
+    OCSPAcceptableResponses,
     OCSPNoCheck,
     OCSPNonce,
     PolicyConstraints,
@@ -196,6 +197,7 @@
     "IssuingDistributionPoint",
     "TLSFeature",
     "TLSFeatureType",
+    "OCSPAcceptableResponses",
     "OCSPNoCheck",
     "BasicConstraints",
     "CRLNumber",
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
@@ -1932,6 +1932,35 @@ def public_bytes(self) -> bytes:
         return rust_x509.encode_extension_value(self)
 
 
+class OCSPAcceptableResponses(ExtensionType):
+    oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES
+
+    def __init__(self, responses: typing.Iterable[ObjectIdentifier]) -> None:
+        responses = list(responses)
+        if any(not isinstance(r, ObjectIdentifier) for r in responses):
+            raise TypeError("All responses must be ObjectIdentifiers")
+
+        self._responses = responses
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, OCSPAcceptableResponses):
+            return NotImplemented
+
+        return self._responses == other._responses
+
+    def __hash__(self) -> int:
+        return hash(tuple(self._responses))
+
+    def __repr__(self) -> str:
+        return f"<OCSPAcceptableResponses(responses={self._responses})>"
+
+    def __iter__(self) -> typing.Iterator[ObjectIdentifier]:
+        return iter(self._responses)
+
+    def public_bytes(self) -> bytes:
+        return rust_x509.encode_extension_value(self)
+
+
 class IssuingDistributionPoint(ExtensionType):
     oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT
 


Test patch:
diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py
--- a/tests/x509/test_ocsp.py
+++ b/tests/x509/test_ocsp.py
@@ -102,6 +102,18 @@ def test_load_request_with_extensions(self):
             b"{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd"
         )
 
+    def test_load_request_with_acceptable_responses(self):
+        req = _load_data(
+            os.path.join("x509", "ocsp", "req-acceptable-responses.der"),
+            ocsp.load_der_ocsp_request,
+        )
+        assert len(req.extensions) == 1
+        ext = req.extensions[0]
+        assert ext.critical is False
+        assert ext.value == x509.OCSPAcceptableResponses(
+            [x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")]
+        )
+
     def test_load_request_with_unknown_extension(self):
         req = _load_data(
             os.path.join("x509", "ocsp", "req-ext-unknown-oid.der"),
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
@@ -6132,6 +6132,73 @@ def test_public_bytes(self):
         assert ext.public_bytes() == b"\x04\x0500000"
 
 
+class TestOCSPAcceptableResponses:
+    def test_invalid_types(self):
+        with pytest.raises(TypeError):
+            x509.OCSPAcceptableResponses(38)  # type:ignore[arg-type]
+        with pytest.raises(TypeError):
+            x509.OCSPAcceptableResponses([38])  # type:ignore[list-item]
+
+    def test_eq(self):
+        acceptable_responses1 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.3")]
+        )
+        acceptable_responses2 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.3")]
+        )
+        assert acceptable_responses1 == acceptable_responses2
+
+    def test_ne(self):
+        acceptable_responses1 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.3")]
+        )
+        acceptable_responses2 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.4")]
+        )
+        assert acceptable_responses1 != acceptable_responses2
+        assert acceptable_responses1 != object()
+
+    def test_repr(self):
+        acceptable_responses = x509.OCSPAcceptableResponses([])
+        assert (
+            repr(acceptable_responses)
+            == "<OCSPAcceptableResponses(responses=[])>"
+        )
+
+    def test_hash(self):
+        acceptable_responses1 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.3")]
+        )
+        acceptable_responses2 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.3")]
+        )
+        acceptable_responses3 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.4")]
+        )
+
+        assert hash(acceptable_responses1) == hash(acceptable_responses2)
+        assert hash(acceptable_responses1) != hash(acceptable_responses3)
+
+    def test_iter(self):
+        acceptable_responses1 = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.2.3")]
+        )
+
+        assert list(acceptable_responses1) == [ObjectIdentifier("1.2.3")]
+
+    def test_public_bytes(self):
+        ext = x509.OCSPAcceptableResponses([])
+        assert ext.public_bytes() == b"\x30\x00"
+
+        ext = x509.OCSPAcceptableResponses(
+            [ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")]
+        )
+        assert (
+            ext.public_bytes()
+            == b"\x30\x0b\x06\t+\x06\x01\x05\x05\x07\x30\x01\x01"
+        )
+
+
 def test_all_extension_oid_members_have_names_defined():
     for oid in dir(ExtensionOID):
         if oid.startswith("__"):