diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2018-09-09 21:57:21 -0500 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2018-09-09 22:57:21 -0400 |
commit | 09403100de2f6f1cdd0d484dcb8e620f1c335c8f (patch) | |
tree | f128b1a1c5ad82e4c372091758fa65b6d6f1ed3b | |
parent | 15827f1fcb7459aac7dbe43c373a826f69a09c0c (diff) | |
download | cryptography-09403100de2f6f1cdd0d484dcb8e620f1c335c8f.tar.gz cryptography-09403100de2f6f1cdd0d484dcb8e620f1c335c8f.tar.bz2 cryptography-09403100de2f6f1cdd0d484dcb8e620f1c335c8f.zip |
OCSP request extension parsing (#4464)
* add OCSP request parsing support with OCSPNonce
* add docs
* reprs man
* make extensions a cached property
-rw-r--r-- | docs/spelling_wordlist.txt | 1 | ||||
-rw-r--r-- | docs/x509/ocsp.rst | 6 | ||||
-rw-r--r-- | docs/x509/reference.rst | 33 | ||||
-rw-r--r-- | src/_cffi_src/openssl/ocsp.py | 2 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/decode_asn1.py | 19 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/ocsp.py | 6 | ||||
-rw-r--r-- | src/cryptography/x509/__init__.py | 5 | ||||
-rw-r--r-- | src/cryptography/x509/extensions.py | 30 | ||||
-rw-r--r-- | src/cryptography/x509/ocsp.py | 6 | ||||
-rw-r--r-- | src/cryptography/x509/oid.py | 5 | ||||
-rw-r--r-- | tests/x509/test_ocsp.py | 13 | ||||
-rw-r--r-- | tests/x509/test_x509_ext.py | 31 |
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) |