support extensions in the OCSP request builder (#4481)

* support extensions in the OCSP request builder

* cover a missed branch

* refactor to use new func

* review feedback
diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst
index bf06413..528502a 100644
--- a/docs/x509/ocsp.rst
+++ b/docs/x509/ocsp.rst
@@ -133,6 +133,16 @@
             :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and
             :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed.
 
+    .. method:: add_extension(extension, critical)
+
+        Adds an extension to the request.
+
+        :param extension: An extension conforming to the
+            :class:`~cryptography.x509.ExtensionType` interface.
+
+        :param critical: Set to ``True`` if the extension must be understood and
+             handled.
+
     .. method:: build()
 
         :returns: A new :class:`~cryptography.x509.ocsp.OCSPRequest`.
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 8118cad..5d0a444 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -38,6 +38,7 @@
 from cryptography.hazmat.backends.openssl.encode_asn1 import (
     _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS,
     _CRL_EXTENSION_ENCODE_HANDLERS, _EXTENSION_ENCODE_HANDLERS,
+    _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS,
     _encode_asn1_int_gc, _encode_asn1_str_gc, _encode_name_gc, _txt2obj_gc,
 )
 from cryptography.hazmat.backends.openssl.hashes import _HashContext
@@ -1465,6 +1466,13 @@
         self.openssl_assert(certid != self._ffi.NULL)
         onereq = self._lib.OCSP_request_add0_id(ocsp_req, certid)
         self.openssl_assert(onereq != self._ffi.NULL)
+        self._create_x509_extensions(
+            extensions=builder._extensions,
+            handlers=_OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS,
+            x509_obj=ocsp_req,
+            add_func=self._lib.OCSP_REQUEST_add_ext,
+            gc=True,
+        )
         return _OCSPRequest(self, ocsp_req)
 
     def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve):
diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
index 91852df..c8b41a8 100644
--- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py
@@ -15,7 +15,9 @@
     _DISTPOINT_TYPE_RELATIVENAME
 )
 from cryptography.x509.name import _ASN1Type
-from cryptography.x509.oid import CRLEntryExtensionOID, ExtensionOID
+from cryptography.x509.oid import (
+    CRLEntryExtensionOID, ExtensionOID, OCSPExtensionOID,
+)
 
 
 def _encode_asn1_int(backend, x):
@@ -569,6 +571,10 @@
         return general_subtrees
 
 
+def _encode_nonce(backend, nonce):
+    return _encode_asn1_str_gc(backend, nonce.nonce)
+
+
 _EXTENSION_ENCODE_HANDLERS = {
     ExtensionOID.BASIC_CONSTRAINTS: _encode_basic_constraints,
     ExtensionOID.SUBJECT_KEY_IDENTIFIER: _encode_subject_key_identifier,
@@ -604,3 +610,7 @@
     CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason,
     CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date,
 }
+
+_OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS = {
+    OCSPExtensionOID.NONCE: _encode_nonce,
+}
diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py
index fbf1133..c89f12c 100644
--- a/src/cryptography/x509/ocsp.py
+++ b/src/cryptography/x509/ocsp.py
@@ -9,8 +9,9 @@
 
 import six
 
+from cryptography import x509
 from cryptography.hazmat.primitives import hashes
-from cryptography.x509 import Certificate
+from cryptography.x509.base import _reject_duplicate_extension
 
 
 _OIDS_TO_HASH = {
@@ -54,8 +55,9 @@
 
 
 class OCSPRequestBuilder(object):
-    def __init__(self, request=None):
+    def __init__(self, request=None, extensions=[]):
         self._request = request
+        self._extensions = extensions
 
     def add_certificate(self, cert, issuer, algorithm):
         if self._request is not None:
@@ -70,12 +72,23 @@
                 "Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512"
             )
         if (
-            not isinstance(cert, Certificate) or
-            not isinstance(issuer, Certificate)
+            not isinstance(cert, x509.Certificate) or
+            not isinstance(issuer, x509.Certificate)
         ):
             raise TypeError("cert and issuer must be a Certificate")
 
-        return OCSPRequestBuilder((cert, issuer, algorithm))
+        return OCSPRequestBuilder((cert, issuer, algorithm), self._extensions)
+
+    def add_extension(self, extension, critical):
+        if not isinstance(extension, x509.ExtensionType):
+            raise TypeError("extension must be an ExtensionType")
+
+        extension = x509.Extension(extension.oid, critical, extension)
+        _reject_duplicate_extension(extension, self._extensions)
+
+        return OCSPRequestBuilder(
+            self._request, self._extensions + [extension]
+        )
 
     def build(self):
         from cryptography.hazmat.backends.openssl.backend import backend
diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py
index 34ad11b..483387a 100644
--- a/tests/hazmat/backends/test_openssl_memleak.py
+++ b/tests/hazmat/backends/test_openssl_memleak.py
@@ -286,3 +286,24 @@
             private_key = x25519.X25519PrivateKey.generate()
             private_key.public_key()
         """))
+
+    def test_create_ocsp_request(self):
+        assert_no_memory_leaks(textwrap.dedent("""
+        def func():
+            from cryptography import x509
+            from cryptography.hazmat.backends.openssl import backend
+            from cryptography.hazmat.primitives import hashes
+            from cryptography.x509 import ocsp
+            import cryptography_vectors
+
+            path = "x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt"
+            with cryptography_vectors.open_vector_file(path, "rb") as f:
+                cert = x509.load_der_x509_certificate(
+                    f.read(), backend
+                )
+            builder = ocsp.OCSPRequestBuilder()
+            builder = builder.add_certificate(
+                cert, cert, hashes.SHA1()
+            ).add_extension(x509.OCSPNonce(b"0000"), False)
+            req = builder.build()
+        """))
diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py
index 0d98ac2..d680e07 100644
--- a/tests/x509/test_ocsp.py
+++ b/tests/x509/test_ocsp.py
@@ -129,6 +129,17 @@
         with pytest.raises(ValueError):
             builder.add_certificate(cert, issuer, hashes.MD5())
 
+    def test_add_extension_twice(self):
+        builder = ocsp.OCSPRequestBuilder()
+        builder = builder.add_extension(x509.OCSPNonce(b"123"), False)
+        with pytest.raises(ValueError):
+            builder.add_extension(x509.OCSPNonce(b"123"), False)
+
+    def test_add_invalid_extension(self):
+        builder = ocsp.OCSPRequestBuilder()
+        with pytest.raises(TypeError):
+            builder.add_extension("notanext", False)
+
     def test_create_ocsp_request_invalid_cert(self):
         cert, issuer = _cert_and_issuer()
         builder = ocsp.OCSPRequestBuilder()
@@ -149,6 +160,27 @@
             b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g"
         )
 
+    @pytest.mark.parametrize(
+        ("ext", "critical"),
+        [
+            [x509.OCSPNonce(b"0000"), False],
+            [x509.OCSPNonce(b"\x00\x01\x02"), True],
+        ]
+    )
+    def test_create_ocsp_request_with_extension(self, ext, critical):
+        cert, issuer = _cert_and_issuer()
+        builder = ocsp.OCSPRequestBuilder()
+        builder = builder.add_certificate(
+            cert, issuer, hashes.SHA1()
+        ).add_extension(
+            ext, critical
+        )
+        req = builder.build()
+        assert len(req.extensions) == 1
+        assert req.extensions[0].value == ext
+        assert req.extensions[0].oid == ext.oid
+        assert req.extensions[0].critical is critical
+
 
 class TestOCSPResponse(object):
     def test_bad_response(self):