aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/spelling_wordlist.txt1
-rw-r--r--docs/x509/ocsp.rst6
-rw-r--r--docs/x509/reference.rst33
-rw-r--r--src/_cffi_src/openssl/ocsp.py2
-rw-r--r--src/cryptography/hazmat/backends/openssl/decode_asn1.py19
-rw-r--r--src/cryptography/hazmat/backends/openssl/ocsp.py6
-rw-r--r--src/cryptography/x509/__init__.py5
-rw-r--r--src/cryptography/x509/extensions.py30
-rw-r--r--src/cryptography/x509/ocsp.py6
-rw-r--r--src/cryptography/x509/oid.py5
-rw-r--r--tests/x509/test_ocsp.py13
-rw-r--r--tests/x509/test_x509_ext.py31
12 files changed, 152 insertions, 5 deletions
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index e8b9098f..ed189248 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -76,6 +76,7 @@ personalization
pickleable
plaintext
pre
+precompute
preprocessor
preprocessors
presentational
diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst
index b706b323..163a6a8a 100644
--- a/docs/x509/ocsp.rst
+++ b/docs/x509/ocsp.rst
@@ -190,6 +190,12 @@ Interfaces
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 ede08aa5..079fef92 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -2432,6 +2432,30 @@ These extensions are only valid within a :class:`RevokedCertificate` object.
: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 @@ instances. The following common OIDs are available as constants.
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 db8597af..a466458d 100644
--- a/src/_cffi_src/openssl/ocsp.py
+++ b/src/_cffi_src/openssl/ocsp.py
@@ -40,6 +40,8 @@ X509_EXTENSION *OCSP_SINGLERESP_get_ext(OCSP_SINGLERESP *, int);
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 47fa911e..80309980 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 import x509
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 @@ def _parse_asn1_generalized_time(backend, generalized_time):
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 @@ _CRL_EXTENSION_HANDLERS = {
),
}
+_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 @@ _CRL_EXTENSION_PARSER = _X509ExtensionParser(
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 2b07b324..420d7eb6 100644
--- a/src/cryptography/hazmat/backends/openssl/ocsp.py
+++ b/src/cryptography/hazmat/backends/openssl/ocsp.py
@@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function
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 @@ class _OCSPRequest(object):
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 15459a12..fd019455 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -21,8 +21,8 @@ from cryptography.x509.extensions import (
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 @@ __all__ = [
"PolicyConstraints",
"PrecertificateSignedCertificateTimestamps",
"PrecertPoison",
+ "OCSPNonce",
]
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index 08af03c8..b2d9908e 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -24,7 +24,7 @@ from cryptography.x509.certificate_transparency import (
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 @@ class PrecertificateSignedCertificateTimestamps(object):
@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 95e7f35b..7535a0b3 100644
--- a/src/cryptography/x509/ocsp.py
+++ b/src/cryptography/x509/ocsp.py
@@ -108,6 +108,12 @@ class OCSPRequest(object):
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 77e3fa63..bc654640 100644
--- a/src/cryptography/x509/oid.py
+++ b/src/cryptography/x509/oid.py
@@ -96,6 +96,10 @@ class ExtensionOID(object):
)
+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 @@ _OID_NAMES = {
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 3e6ac9cd..a646f4b7 100644
--- a/tests/x509/test_ocsp.py
+++ b/tests/x509/test_ocsp.py
@@ -59,6 +59,19 @@ class TestOCSPRequest(object):
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 7e0ae220..7a43c851 100644
--- a/tests/x509/test_x509_ext.py
+++ b/tests/x509/test_x509_ext.py
@@ -4547,3 +4547,34 @@ class TestInvalidExtension(object):
)
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)