Skip to content
Permalink
Browse files

OCSP request extension parsing (#4464)

* add OCSP request parsing support with OCSPNonce

* add docs

* reprs man

* make extensions a cached property
  • Loading branch information...
reaperhulk authored and alex committed Sep 10, 2018
1 parent 15827f1 commit 09403100de2f6f1cdd0d484dcb8e620f1c335c8f
@@ -76,6 +76,7 @@ personalization
pickleable
plaintext
pre
precompute
preprocessor
preprocessors
presentational
@@ -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
@@ -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
@@ -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 *);
@@ -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 @@ 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 @@ def _parse_asn1_generalized_time(backend, generalized_time):
),
}

_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 @@ def _parse_asn1_generalized_time(backend, generalized_time):
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,
)
@@ -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 serial_number(self):
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(
@@ -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",
]
@@ -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,
)


@@ -1403,6 +1403,34 @@ def __repr__(self):
)


@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):
@@ -108,6 +108,12 @@ def public_bytes(self, encoding):
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):
@@ -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 @@ class CertificatePoliciesOID(object):
AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers",
CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps",
CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice",
OCSPExtensionOID.NONCE: "OCSPNonce",
}
@@ -59,6 +59,19 @@ def test_load_request(self):
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):
@@ -4547,3 +4547,34 @@ def test_invalid_certificate_policies_data(self, backend):
)
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)

0 comments on commit 0940310

Please sign in to comment.
You can’t perform that action at this time.