make an ocsp request (#4402)

* make an ocsp request

* update test, add docs

* make it an OCSPRequestBuilder

* review feedback and more tests

* make it a class

* empty commit to retrigger

* type check
diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst
index 72227f0..afbb2ef 100644
--- a/docs/x509/ocsp.rst
+++ b/docs/x509/ocsp.rst
@@ -5,6 +5,69 @@
 
 .. testsetup::
 
+    import base64
+    pem_cert = b"""
+    -----BEGIN CERTIFICATE-----
+    MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx
+    FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1
+    NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR
+    BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t
+    L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh
+    bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5
+    LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s
+    itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR
+    PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ
+    CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu
+    6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y
+    3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/
+    r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW
+    ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx
+    diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi
+    gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu
+    YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74
+    FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc
+    8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT
+    aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi
+    LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+    BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw
+    dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv
+    bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw
+    LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G
+    CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc
+    dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt
+    Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF
+    7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH
+    aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i
+    GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP
+    -----END CERTIFICATE-----
+    """
+    pem_issuer = b"""
+    -----BEGIN CERTIFICATE-----
+    MIIEJTCCAw2gAwIBAgIDAjp3MA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT
+    MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+    YWwgQ0EwHhcNMTQwODI5MjEzOTMyWhcNMjIwNTIwMjEzOTMyWjBHMQswCQYDVQQG
+    EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXUmFwaWRTU0wg
+    U0hBMjU2IENBIC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv
+    VJvZWF0eLFbG1eh/9H0WA//Qi1rkjqfdVC7UBMBdmJyNkA+8EGVf2prWRHzAn7Xp
+    SowLBkMEu/SW4ib2YQGRZjEiwzQ0Xz8/kS9EX9zHFLYDn4ZLDqP/oIACg8PTH2lS
+    1p1kD8mD5xvEcKyU58Okaiy9uJ5p2L4KjxZjWmhxgHsw3hUEv8zTvz5IBVV6s9cQ
+    DAP8m/0Ip4yM26eO8R5j3LMBL3+vV8M8SKeDaCGnL+enP/C1DPz1hNFTvA5yT2AM
+    QriYrRmIV9cE7Ie/fodOoyH5U/02mEiN1vi7SPIpyGTRzFRIU4uvt2UevykzKdkp
+    YEj4/5G8V1jlNS67abZZAgMBAAGjggEdMIIBGTAfBgNVHSMEGDAWgBTAephojYn7
+    qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUw5zz/NNGCDS7zkZ/oHxb8+IIy1kwEgYD
+    VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwNQYDVR0fBC4wLDAqoCig
+    JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMC4GCCsGAQUF
+    BwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMEwGA1UdIARF
+    MEMwQQYKYIZIAYb4RQEHNjAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3Lmdlb3Ry
+    dXN0LmNvbS9yZXNvdXJjZXMvY3BzMA0GCSqGSIb3DQEBCwUAA4IBAQCjWB7GQzKs
+    rC+TeLfqrlRARy1+eI1Q9vhmrNZPc9ZE768LzFvB9E+aj0l+YK/CJ8cW8fuTgZCp
+    fO9vfm5FlBaEvexJ8cQO9K8EWYOHDyw7l8NaEpt7BDV7o5UzCHuTcSJCs6nZb0+B
+    kvwHtnm8hEqddwnxxYny8LScVKoSew26T++TGezvfU5ho452nFnPjJSxhJf3GrkH
+    uLLGTxN5279PURt/aQ1RKsHWFf83UTRlUfQevjhq7A6rvz17OQV79PP7GqHQyH5O
+    ZI3NjGFVkP46yl0lD/gdo0p0Vk8aVUBwdSWmMy66S6VdU5oNMOGNX2Esr8zvsJmh
+    gP8L8mJMcCaY
+    -----END CERTIFICATE-----
+    """
     der_ocsp_req = (
         b"0V0T0R0P0N0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x148\xcaF\x8c"
         b"\x07D\x8d\xf4\x81\x96\xc7mmLpQ\x9e`\xa7\xbd\x04\x14yu\xbb\x84:\xcb"
@@ -39,6 +102,56 @@
         872625873161273451176241581705670534707360122361
 
 
+Creating Requests
+~~~~~~~~~~~~~~~~~
+
+.. class:: OCSPRequestBuilder
+
+    .. versionadded:: 2.4
+
+    This class is used to create :class:`~cryptography.x509.ocsp.OCSPRequest`
+    objects.
+
+
+    .. method:: add_request(cert, issuer, algorithm)
+
+        Adds a request using a certificate, issuer certificate, and hash
+        algorithm.
+
+        :param cert: The :class:`~cryptography.x509.Certificate` whose validity
+            is being checked.
+
+        :param issuer: The issuer :class:`~cryptography.x509.Certificate` of
+            the certificate that is being checked.
+
+        :param algorithm: A
+            :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
+            instance. For OCSP only
+            :class:`~cryptography.hazmat.primitives.hashes.SHA1`,
+            :class:`~cryptography.hazmat.primitives.hashes.SHA224`,
+            :class:`~cryptography.hazmat.primitives.hashes.SHA256`,
+            :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and
+            :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed.
+
+    .. method:: build()
+
+        :returns: A new :class:`~cryptography.x509.ocsp.OCSPRequest`.
+
+    .. doctest::
+
+        >>> from cryptography.hazmat.backends import default_backend
+        >>> from cryptography.hazmat.primitives import serialization
+        >>> from cryptography.hazmat.primitives.hashes import SHA256
+        >>> from cryptography.x509 import load_pem_x509_certificate, ocsp
+        >>> cert = load_pem_x509_certificate(pem_cert, default_backend())
+        >>> issuer = load_pem_x509_certificate(pem_issuer, default_backend())
+        >>> builder = ocsp.OCSPRequestBuilder()
+        >>> builder = builder.add_request(cert, issuer, SHA256())
+        >>> req = builder.build()
+        >>> base64.b64encode(req.public_bytes(serialization.Encoding.DER))
+        b'MF8wXTBbMFkwVzANBglghkgBZQMEAgEFAAQgn3BowBaoh77h17ULfkX6781dUDPD82Taj8wO1jZWhZoEINxPgjoQth3w7q4AouKKerMxIMIuUG4EuWU2pZfwih52AgI/IA=='
+
+
 Interfaces
 ~~~~~~~~~~
 
diff --git a/src/_cffi_src/openssl/ocsp.py b/src/_cffi_src/openssl/ocsp.py
index 1701f41..6154602 100644
--- a/src/_cffi_src/openssl/ocsp.py
+++ b/src/_cffi_src/openssl/ocsp.py
@@ -35,6 +35,8 @@
 int OCSP_ONEREQ_get_ext_count(OCSP_ONEREQ *);
 X509_EXTENSION *OCSP_ONEREQ_get_ext(OCSP_ONEREQ *, int);
 OCSP_CERTID *OCSP_onereq_get0_id(OCSP_ONEREQ *);
+OCSP_ONEREQ *OCSP_request_add0_id(OCSP_REQUEST *, OCSP_CERTID *);
+OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *, const X509 *, const X509 *);
 
 
 OCSP_BASICRESP *OCSP_BASICRESP_new(void);
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 6a0446b..bdf8f37 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1430,6 +1430,22 @@
         request = self._ffi.gc(request, self._lib.OCSP_REQUEST_free)
         return _OCSPRequest(self, request)
 
+    def create_ocsp_request(self, builder):
+        ocsp_req = self._lib.OCSP_REQUEST_new()
+        self.openssl_assert(ocsp_req != self._ffi.NULL)
+        ocsp_req = self._ffi.gc(ocsp_req, self._lib.OCSP_REQUEST_free)
+        for cert, issuer, algorithm in builder._requests:
+            evp_md = self._lib.EVP_get_digestbyname(
+                algorithm.name.encode("ascii"))
+            self.openssl_assert(evp_md != self._ffi.NULL)
+            certid = self._lib.OCSP_cert_to_id(
+                evp_md, cert._x509, issuer._x509
+            )
+            self.openssl_assert(certid != self._ffi.NULL)
+            onereq = self._lib.OCSP_request_add0_id(ocsp_req, certid)
+            self.openssl_assert(onereq != self._ffi.NULL)
+        return _OCSPRequest(self, ocsp_req)
+
     def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve):
         return (
             self.elliptic_curve_supported(curve) and
diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py
index 22894dd..0567197 100644
--- a/src/cryptography/x509/ocsp.py
+++ b/src/cryptography/x509/ocsp.py
@@ -9,6 +9,7 @@
 import six
 
 from cryptography.hazmat.primitives import hashes
+from cryptography.x509 import Certificate
 
 
 _OIDS_TO_HASH = {
@@ -25,6 +26,35 @@
     return backend.load_der_ocsp_request(data)
 
 
+class OCSPRequestBuilder(object):
+    def __init__(self, requests=[]):
+        self._requests = requests
+
+    def add_request(self, cert, issuer, algorithm):
+        allowed_hashes = (
+            hashes.SHA1, hashes.SHA224, hashes.SHA256,
+            hashes.SHA384, hashes.SHA512
+        )
+        if not isinstance(algorithm, allowed_hashes):
+            raise ValueError(
+                "Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512"
+            )
+        if (
+            not isinstance(cert, Certificate) or
+            not isinstance(issuer, Certificate)
+        ):
+            raise TypeError("cert and issuer must be a Certificate")
+
+        return OCSPRequestBuilder(self._requests + [(cert, issuer, algorithm)])
+
+    def build(self):
+        from cryptography.hazmat.backends.openssl.backend import backend
+        if len(self._requests) == 0:
+            raise ValueError("You must add a request before building")
+
+        return backend.create_ocsp_request(self)
+
+
 @six.add_metaclass(abc.ABCMeta)
 class OCSPRequest(object):
     @abc.abstractmethod
diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py
index 22f34df..709ef6f 100644
--- a/tests/x509/test_ocsp.py
+++ b/tests/x509/test_ocsp.py
@@ -4,14 +4,17 @@
 
 from __future__ import absolute_import, division, print_function
 
+import base64
 import os
 
 import pytest
 
+from cryptography import x509
 from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.x509 import ocsp
 
+from .test_x509 import _load_cert
 from ..utils import load_vectors_from_file
 
 
@@ -23,6 +26,21 @@
     )
 
 
+def _cert_and_issuer():
+    from cryptography.hazmat.backends.openssl.backend import backend
+    cert = _load_cert(
+        os.path.join("x509", "cryptography.io.pem"),
+        x509.load_pem_x509_certificate,
+        backend
+    )
+    issuer = _load_cert(
+        os.path.join("x509", "rapidssl_sha256_ca_g3.pem"),
+        x509.load_pem_x509_certificate,
+        backend
+    )
+    return cert, issuer
+
+
 class TestOCSPRequest(object):
     def test_bad_request(self):
         with pytest.raises(ValueError):
@@ -113,3 +131,49 @@
             req.public_bytes("invalid")
         with pytest.raises(ValueError):
             req.public_bytes(serialization.Encoding.PEM)
+
+
+class TestOCSPRequestBuilder(object):
+    def test_create_ocsp_request_no_req(self):
+        builder = ocsp.OCSPRequestBuilder()
+        with pytest.raises(ValueError):
+            builder.build()
+
+    def test_create_ocsp_request_invalid_alg(self):
+        cert, issuer = _cert_and_issuer()
+        builder = ocsp.OCSPRequestBuilder()
+        with pytest.raises(ValueError):
+            builder.add_request(cert, issuer, hashes.MD5())
+
+    def test_create_ocsp_request_invalid_cert(self):
+        cert, issuer = _cert_and_issuer()
+        builder = ocsp.OCSPRequestBuilder()
+        with pytest.raises(TypeError):
+            builder.add_request(b"notacert", issuer, hashes.SHA1())
+
+        with pytest.raises(TypeError):
+            builder.add_request(cert, b"notacert", hashes.SHA1())
+
+    def test_create_ocsp_request(self):
+        cert, issuer = _cert_and_issuer()
+        builder = ocsp.OCSPRequestBuilder()
+        builder = builder.add_request(cert, issuer, hashes.SHA1())
+        req = builder.build()
+        serialized = req.public_bytes(serialization.Encoding.DER)
+        assert serialized == base64.b64decode(
+            b"MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz"
+            b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g"
+        )
+
+    def test_create_ocsp_request_two_reqs(self):
+        builder = ocsp.OCSPRequestBuilder()
+        cert, issuer = _cert_and_issuer()
+        builder = builder.add_request(cert, issuer, hashes.SHA1())
+        builder = builder.add_request(cert, issuer, hashes.SHA1())
+        req = builder.build()
+        serialized = req.public_bytes(serialization.Encoding.DER)
+        assert serialized == base64.b64decode(
+            b"MIGDMIGAMH4wPTA7MAkGBSsOAwIaBQAEFEALRnrx5rLTCYO6DWB+flk3SCTEBBTD"
+            b"nPP800YINLvORn+gfFvz4gjLWQICPyAwPTA7MAkGBSsOAwIaBQAEFEALRnrx5rLT"
+            b"CYO6DWB+flk3SCTEBBTDnPP800YINLvORn+gfFvz4gjLWQICPyA="
+        )