Repository: pyca/cryptography

Issue:
openssl.Backend.load_der_ocsp_response() failing to load complex response
* Versions of Python, ``cryptography``, ``cffi``, ``pip``, and ``setuptools``
  you're using
```
Name: cryptography
Version: 2.8
---
Name: cffi
Version: 1.11.5
---
Name: pip
Version: 18.1
---
Name: setuptools
Version: 40.8.0
```

* How you installed ``cryptography``
`pip install cryptography --upgrade`

* Clear steps for reproducing your bug
Code:
```python
from cryptography.hazmat.backends.openssl.backend import backend as x509_openssl_backend
ocsp_response_der = open('ocsp-army.deps.mil-resp.der', 'rb').read()
x509_openssl_backend.load_der_ocsp_response(ocsp_response_der )
```

Error:
```
    ocsp_resp = x509_openssl_backend.load_der_ocsp_response(r.content)
  File "/usr/local/lib64/python3.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1531, in load_der_ocsp_response
    return _OCSPResponse(self, response)
  File "/usr/local/lib64/python3.7/site-packages/cryptography/hazmat/backends/openssl/ocsp.py", line 106, in __init__
    self._backend._lib.OCSP_resp_count(self._basic) == 1
  File "/usr/local/lib64/python3.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 131, in openssl_assert
    return binding._openssl_assert(self._lib, ok)
  File "/usr/local/lib64/python3.7/site-packages/cryptography/hazmat/bindings/openssl/binding.py", line 78, in _openssl_assert
    errors_with_text
cryptography.exceptions.InternalError: Unknown OpenSSL error. This error is commonly encountered when another library is not cleaning up the OpenSSL error stack.
```

The specific OCSP-response file is attached as a Zip-file.
[ocsp-army.deps.mil-resp.zip](https://github.com/pyca/cryptography/files/4239418/ocsp-army.deps.mil-resp.zip)

* Additional information:
Running:
```
$ openssl ocsp -respin ocsp-army.deps.mil-resp.der -text
```
will produce correct and expected output without errors.

This error is highly data-dependant and very reproducible. For some other data, the Python code works. For this particular data everything explodes. My thinking is, this data is worst-case-scenario. The OCSP-response has all kinds of extensions in it.


Discussion:
Okay, looks like this OCSP responder produces multiple SINGLERESP structures... This will require more investigation, because the current API can't gracefully handle this case.
Yes, that particular responder went haywire. Number of issues there. Probably the OCSP-response doesn't make any sense, even. However, that's beside the point.

All I'd need is the Python-library not to explode with an internal error on me. The response isn't **that** bad to warrant the current behavior, a complete meltdown. Also, as this data was seen in a real server in The Net, some error/sanity checking resulting in an exception is preferred.

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/ocsp.py b/src/cryptography/hazmat/backends/openssl/ocsp.py
--- a/src/cryptography/hazmat/backends/openssl/ocsp.py
+++ b/src/cryptography/hazmat/backends/openssl/ocsp.py
@@ -104,9 +104,13 @@ def __init__(self, backend, ocsp_response):
             self._basic = self._backend._ffi.gc(
                 basic, self._backend._lib.OCSP_BASICRESP_free
             )
-            self._backend.openssl_assert(
-                self._backend._lib.OCSP_resp_count(self._basic) == 1
-            )
+            num_resp = self._backend._lib.OCSP_resp_count(self._basic)
+            if num_resp != 1:
+                raise ValueError(
+                    "OCSP response contains more than one SINGLERESP structure"
+                    ", which this library does not support. "
+                    "{} found".format(num_resp)
+                )
             self._single = self._backend._lib.OCSP_resp_get0(self._basic, 0)
             self._backend.openssl_assert(
                 self._single != self._backend._ffi.NULL


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
@@ -695,6 +695,13 @@ def test_load_response(self):
         assert resp.serial_number == 271024907440004808294641238224534273948400
         assert len(resp.extensions) == 0
 
+    def test_load_multi_valued_response(self):
+        with pytest.raises(ValueError):
+            _load_data(
+                os.path.join("x509", "ocsp", "ocsp-army.deps.mil-resp.der"),
+                ocsp.load_der_ocsp_response,
+            )
+
     def test_load_unauthorized(self):
         resp = _load_data(
             os.path.join("x509", "ocsp", "resp-unauthorized.der"),