OCSP request extension parsing (#4464)

* add OCSP request parsing support with OCSPNonce

* add docs

* reprs man

* make extensions a cached property
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index e8b9098..ed18924 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -76,6 +76,7 @@
 pickleable
 plaintext
 pre
+precompute
 preprocessor
 preprocessors
 presentational
diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst
index b706b32..163a6a8 100644
--- a/docs/x509/ocsp.rst
+++ b/docs/x509/ocsp.rst
@@ -190,6 +190,12 @@
 
         The serial number of the certificate to check.
 
+    .. attribute:: extensions
+
+        :type: :class:`~cryptography.x509.Extensions`
+
+        The extensions encoded in the request.
+
     .. method:: public_bytes(encoding)
 
         :param encoding: The encoding to use. Only
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index ede08aa..079fef9 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -2432,6 +2432,30 @@
 
         :type: :class:`datetime.datetime`
 
+OCSP Extensions
+~~~~~~~~~~~~~~~
+
+.. class:: OCSPNonce(nonce)
+
+    .. versionadded:: 2.4
+
+    OCSP nonce is an extension that is only valid inside
+    :class:`~cryptography.x509.ocsp.OCSPRequest` and
+    :class:`~cryptography.x509.ocsp.OCSPResponse` objects. The nonce
+    cryptographically binds a request and a response to prevent replay attacks.
+    In practice nonces are rarely used in OCSP due to the desire to precompute
+    OCSP responses at large scale.
+
+    .. attribute:: oid
+
+        :type: :class:`ObjectIdentifier`
+
+        Returns
+        :attr:`~cryptography.x509.oid.OCSPExtensionOID.NONCE`.
+
+    .. attribute:: nonce
+
+        :type: bytes
 
 Object Identifiers
 ~~~~~~~~~~~~~~~~~~
@@ -2854,6 +2878,15 @@
 
         Corresponds to the dotted string ``"2.5.29.24"``.
 
+
+.. class:: OCSPExtensionOID
+
+    .. versionadded:: 2.4
+
+    .. attribute:: NONCE
+
+        Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.2"``.
+
 Helper Functions
 ~~~~~~~~~~~~~~~~
 .. currentmodule:: cryptography.x509
diff --git a/src/_cffi_src/openssl/ocsp.py b/src/_cffi_src/openssl/ocsp.py
index db8597a..a466458 100644
--- a/src/_cffi_src/openssl/ocsp.py
+++ b/src/_cffi_src/openssl/ocsp.py
@@ -40,6 +40,8 @@
 int OCSP_single_get0_status(OCSP_SINGLERESP *, int *, ASN1_GENERALIZEDTIME **,
                             ASN1_GENERALIZEDTIME **, ASN1_GENERALIZEDTIME **);
 
+int OCSP_REQUEST_get_ext_count(OCSP_REQUEST *);
+X509_EXTENSION *OCSP_REQUEST_get_ext(OCSP_REQUEST *, int);
 int OCSP_request_onereq_count(OCSP_REQUEST *);
 OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *, int);
 int OCSP_ONEREQ_get_ext_count(OCSP_ONEREQ *);
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
index 47fa911..8030998 100644
--- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
@@ -13,7 +13,8 @@
 from cryptography.x509.extensions import _TLS_FEATURE_TYPE_TO_ENUM
 from cryptography.x509.name import _ASN1_TYPE_TO_ENUM
 from cryptography.x509.oid import (
-    CRLEntryExtensionOID, CertificatePoliciesOID, ExtensionOID
+    CRLEntryExtensionOID, CertificatePoliciesOID, ExtensionOID,
+    OCSPExtensionOID,
 )
 
 
@@ -765,6 +766,12 @@
     return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ")
 
 
+def _decode_nonce(backend, nonce):
+    nonce = backend._ffi.cast("ASN1_OCTET_STRING *", nonce)
+    nonce = backend._ffi.gc(nonce, backend._lib.ASN1_OCTET_STRING_free)
+    return x509.OCSPNonce(_asn1_string_to_bytes(backend, nonce))
+
+
 _EXTENSION_HANDLERS_NO_SCT = {
     ExtensionOID.BASIC_CONSTRAINTS: _decode_basic_constraints,
     ExtensionOID.SUBJECT_KEY_IDENTIFIER: _decode_subject_key_identifier,
@@ -806,6 +813,10 @@
     ),
 }
 
+_OCSP_REQ_EXTENSION_HANDLERS = {
+    OCSPExtensionOID.NONCE: _decode_nonce,
+}
+
 _CERTIFICATE_EXTENSION_PARSER_NO_SCT = _X509ExtensionParser(
     ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x),
     get_ext=lambda backend, x, i: backend._lib.X509_get_ext(x, i),
@@ -835,3 +846,9 @@
     get_ext=lambda backend, x, i: backend._lib.X509_CRL_get_ext(x, i),
     handlers=_CRL_EXTENSION_HANDLERS,
 )
+
+_OCSP_REQ_EXT_PARSER = _X509ExtensionParser(
+    ext_count=lambda backend, x: backend._lib.OCSP_REQUEST_get_ext_count(x),
+    get_ext=lambda backend, x, i: backend._lib.OCSP_REQUEST_get_ext(x, i),
+    handlers=_OCSP_REQ_EXTENSION_HANDLERS,
+)
diff --git a/src/cryptography/hazmat/backends/openssl/ocsp.py b/src/cryptography/hazmat/backends/openssl/ocsp.py
index 2b07b32..420d7eb 100644
--- a/src/cryptography/hazmat/backends/openssl/ocsp.py
+++ b/src/cryptography/hazmat/backends/openssl/ocsp.py
@@ -7,7 +7,7 @@
 from cryptography import utils
 from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.backends.openssl.decode_asn1 import (
-    _asn1_integer_to_int, _asn1_string_to_bytes, _obj2txt
+    _OCSP_REQ_EXT_PARSER, _asn1_integer_to_int, _asn1_string_to_bytes, _obj2txt
 )
 from cryptography.hazmat.primitives import serialization
 from cryptography.x509.ocsp import OCSPRequest, _OIDS_TO_HASH
@@ -95,6 +95,10 @@
     def hash_algorithm(self):
         return _hash_algorithm(self._backend, self._cert_id)
 
+    @utils.cached_property
+    def extensions(self):
+        return _OCSP_REQ_EXT_PARSER.parse(self._backend, self._ocsp_request)
+
     def public_bytes(self, encoding):
         if encoding is not serialization.Encoding.DER:
             raise ValueError(
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index 15459a1..fd01945 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -21,8 +21,8 @@
     DeltaCRLIndicator, DistributionPoint, DuplicateExtension, ExtendedKeyUsage,
     Extension, ExtensionNotFound, ExtensionType, Extensions, FreshestCRL,
     GeneralNames, InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName,
-    KeyUsage, NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints,
-    PolicyInformation, PrecertPoison,
+    KeyUsage, NameConstraints, NoticeReference, OCSPNoCheck, OCSPNonce,
+    PolicyConstraints, PolicyInformation, PrecertPoison,
     PrecertificateSignedCertificateTimestamps, ReasonFlags,
     SubjectAlternativeName, SubjectKeyIdentifier, TLSFeature, TLSFeatureType,
     UnrecognizedExtension, UserNotice
@@ -184,4 +184,5 @@
     "PolicyConstraints",
     "PrecertificateSignedCertificateTimestamps",
     "PrecertPoison",
+    "OCSPNonce",
 ]
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index 08af03c..b2d9908 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -24,7 +24,7 @@
 from cryptography.x509.general_name import GeneralName, IPAddress, OtherName
 from cryptography.x509.name import RelativeDistinguishedName
 from cryptography.x509.oid import (
-    CRLEntryExtensionOID, ExtensionOID, ObjectIdentifier
+    CRLEntryExtensionOID, ExtensionOID, OCSPExtensionOID, ObjectIdentifier,
 )
 
 
@@ -1404,6 +1404,34 @@
 
 
 @utils.register_interface(ExtensionType)
+class OCSPNonce(object):
+    oid = OCSPExtensionOID.NONCE
+
+    def __init__(self, nonce):
+        if not isinstance(nonce, bytes):
+            raise TypeError("nonce must be bytes")
+
+        self._nonce = nonce
+
+    def __eq__(self, other):
+        if not isinstance(other, OCSPNonce):
+            return NotImplemented
+
+        return self.nonce == other.nonce
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __hash__(self):
+        return hash(self.nonce)
+
+    def __repr__(self):
+        return "<OCSPNonce(nonce={0.nonce!r})>".format(self)
+
+    nonce = utils.read_only_property("_nonce")
+
+
+@utils.register_interface(ExtensionType)
 class UnrecognizedExtension(object):
     def __init__(self, oid, value):
         if not isinstance(oid, ObjectIdentifier):
diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py
index 95e7f35..7535a0b 100644
--- a/src/cryptography/x509/ocsp.py
+++ b/src/cryptography/x509/ocsp.py
@@ -108,6 +108,12 @@
         Serializes the request to DER
         """
 
+    @abc.abstractproperty
+    def extensions(self):
+        """
+        The list of request extensions. Not single request extensions.
+        """
+
 
 @six.add_metaclass(abc.ABCMeta)
 class OCSPResponse(object):
diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py
index 77e3fa6..bc65464 100644
--- a/src/cryptography/x509/oid.py
+++ b/src/cryptography/x509/oid.py
@@ -96,6 +96,10 @@
     )
 
 
+class OCSPExtensionOID(object):
+    NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2")
+
+
 class CRLEntryExtensionOID(object):
     CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29")
     CRL_REASON = ObjectIdentifier("2.5.29.21")
@@ -271,4 +275,5 @@
     AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers",
     CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps",
     CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice",
+    OCSPExtensionOID.NONCE: "OCSPNonce",
 }
diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py
index 3e6ac9c..a646f4b 100644
--- a/tests/x509/test_ocsp.py
+++ b/tests/x509/test_ocsp.py
@@ -59,6 +59,19 @@
         assert req.serial_number == int(
             "98D9E5C0B4C373552DF77C5D0F1EB5128E4945F9", 16
         )
+        assert len(req.extensions) == 0
+
+    def test_load_request_with_extensions(self):
+        req = _load_data(
+            os.path.join("x509", "ocsp", "req-ext-nonce.der"),
+            ocsp.load_der_ocsp_request,
+        )
+        assert len(req.extensions) == 1
+        ext = req.extensions[0]
+        assert ext.critical is False
+        assert ext.value == x509.OCSPNonce(
+            b"\x04\x10{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd"
+        )
 
     def test_load_request_two_requests(self):
         with pytest.raises(NotImplementedError):
diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py
index 7e0ae22..7a43c85 100644
--- a/tests/x509/test_x509_ext.py
+++ b/tests/x509/test_x509_ext.py
@@ -4547,3 +4547,34 @@
         )
         with pytest.raises(ValueError):
             cert.extensions
+
+
+class TestOCSPNonce(object):
+    def test_non_bytes(self):
+        with pytest.raises(TypeError):
+            x509.OCSPNonce(38)
+
+    def test_eq(self):
+        nonce1 = x509.OCSPNonce(b"0" * 5)
+        nonce2 = x509.OCSPNonce(b"0" * 5)
+        assert nonce1 == nonce2
+
+    def test_ne(self):
+        nonce1 = x509.OCSPNonce(b"0" * 5)
+        nonce2 = x509.OCSPNonce(b"0" * 6)
+        assert nonce1 != nonce2
+        assert nonce1 != object()
+
+    def test_repr(self):
+        nonce1 = x509.OCSPNonce(b"nonce")
+        if not six.PY2:
+            assert repr(nonce1) == "<OCSPNonce(nonce=b'nonce')>"
+        else:
+            assert repr(nonce1) == "<OCSPNonce(nonce='nonce')>"
+
+    def test_hash(self):
+        nonce1 = x509.OCSPNonce(b"0" * 5)
+        nonce2 = x509.OCSPNonce(b"0" * 5)
+        nonce3 = x509.OCSPNonce(b"1" * 5)
+        assert hash(nonce1) == hash(nonce2)
+        assert hash(nonce1) != hash(nonce3)