mirror of
https://github.com/saymrwulf/cryptography.git
synced 2026-05-14 20:37:55 +00:00
Added support for OCSP AcceptableResponses extension (#8617)
fixes #8589
This commit is contained in:
parent
55c13c3114
commit
89228a9deb
12 changed files with 164 additions and 5 deletions
|
|
@ -12,6 +12,8 @@ Changelog
|
|||
removed. Users on older version of OpenSSL will need to upgrade.
|
||||
* **BACKWARDS INCOMPATIBLE:** Support for Python 3.6 has been removed.
|
||||
* Updated the minimum supported Rust version (MSRV) to 1.56.0, from 1.48.0.
|
||||
* Added support for the :class:`~cryptography.x509.OCSPAcceptableResponses`
|
||||
OCSP extension.
|
||||
|
||||
.. _v40-0-1:
|
||||
|
||||
|
|
|
|||
|
|
@ -658,8 +658,10 @@ Custom X.509 OCSP Test Vectors
|
|||
extensions.
|
||||
* ``x509/ocsp/resp-unknown-extension.der`` - An OCSP response containing an
|
||||
extension with an unknown OID.
|
||||
* ``x509/ocsp/resp-unknown-hash-alg.der`` - AN OCSP response containing an
|
||||
* ``x509/ocsp/resp-unknown-hash-alg.der`` - An OCSP response containing an
|
||||
invalid hash algorithm OID.
|
||||
* ``x509/ocsp/req-acceptable-responses.der`` - An OCSP request containing an
|
||||
acceptable responses extension.
|
||||
|
||||
Custom PKCS12 Test Vectors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -2874,6 +2874,29 @@ OCSP Extensions
|
|||
|
||||
:type: bytes
|
||||
|
||||
.. class:: OCSPAcceptableResponses(response)
|
||||
:canonical: cryptography.x509.extensions.OCSPAcceptableResponses
|
||||
|
||||
.. versionadded:: 41.0.0
|
||||
|
||||
OCSP acceptable responses is an extension that is only valid inside
|
||||
:class:`~cryptography.x509.ocsp.OCSPRequest` objects. This allows an OCSP
|
||||
client to tell the server what types of responses it supports. In practice
|
||||
this is rarely used, because there is only one kind of OCSP response in
|
||||
wide use.
|
||||
|
||||
.. attribute:: oid
|
||||
|
||||
:type: :class:`ObjectIdentifier`
|
||||
|
||||
Returns
|
||||
:attr:`~cryptography.x509.oid.OCSPExtensionOID.ACCEPTABLE_RESPONSES`.
|
||||
|
||||
.. attribute:: nonce
|
||||
|
||||
:type: bytes
|
||||
|
||||
|
||||
X.509 Request Attributes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -3509,6 +3532,12 @@ instances. The following common OIDs are available as constants.
|
|||
|
||||
Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.2"``.
|
||||
|
||||
.. attribute:: ACCEPTABLE_RESPONSES
|
||||
|
||||
.. versionadded:: 41.0.0
|
||||
|
||||
Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.4"``.
|
||||
|
||||
|
||||
.. class:: AttributeOID
|
||||
:canonical: cryptography.hazmat._oid.AttributeOID
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class ExtensionOID:
|
|||
|
||||
class OCSPExtensionOID:
|
||||
NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2")
|
||||
ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4")
|
||||
|
||||
|
||||
class CRLEntryExtensionOID:
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ from cryptography.x509.extensions import (
|
|||
KeyUsage,
|
||||
NameConstraints,
|
||||
NoticeReference,
|
||||
OCSPAcceptableResponses,
|
||||
OCSPNoCheck,
|
||||
OCSPNonce,
|
||||
PolicyConstraints,
|
||||
|
|
@ -196,6 +197,7 @@ __all__ = [
|
|||
"IssuingDistributionPoint",
|
||||
"TLSFeature",
|
||||
"TLSFeatureType",
|
||||
"OCSPAcceptableResponses",
|
||||
"OCSPNoCheck",
|
||||
"BasicConstraints",
|
||||
"CRLNumber",
|
||||
|
|
|
|||
|
|
@ -1932,6 +1932,35 @@ class OCSPNonce(ExtensionType):
|
|||
return rust_x509.encode_extension_value(self)
|
||||
|
||||
|
||||
class OCSPAcceptableResponses(ExtensionType):
|
||||
oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES
|
||||
|
||||
def __init__(self, responses: typing.Iterable[ObjectIdentifier]) -> None:
|
||||
responses = list(responses)
|
||||
if any(not isinstance(r, ObjectIdentifier) for r in responses):
|
||||
raise TypeError("All responses must be ObjectIdentifiers")
|
||||
|
||||
self._responses = responses
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, OCSPAcceptableResponses):
|
||||
return NotImplemented
|
||||
|
||||
return self._responses == other._responses
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(tuple(self._responses))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<OCSPAcceptableResponses(responses={self._responses})>"
|
||||
|
||||
def __iter__(self) -> typing.Iterator[ObjectIdentifier]:
|
||||
return iter(self._responses)
|
||||
|
||||
def public_bytes(self) -> bytes:
|
||||
return rust_x509.encode_extension_value(self)
|
||||
|
||||
|
||||
class IssuingDistributionPoint(ExtensionType):
|
||||
oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ pub(crate) fn encode_extension(
|
|||
let ads = x509::common::encode_access_descriptions(ext.py(), ext)?;
|
||||
Ok(Some(asn1::write_single(&ads)?))
|
||||
}
|
||||
&oid::EXTENDED_KEY_USAGE_OID => {
|
||||
&oid::EXTENDED_KEY_USAGE_OID | &oid::ACCEPTABLE_RESPONSES_OID => {
|
||||
let mut oids = vec![];
|
||||
for el in ext.iter()? {
|
||||
let oid = py_oid_to_oid(el?)?;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
// for complete details.
|
||||
|
||||
use crate::asn1::{big_byte_slice_to_py_int, py_uint_to_big_endian_bytes};
|
||||
use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes};
|
||||
use crate::error::{CryptographyError, CryptographyResult};
|
||||
use crate::x509;
|
||||
use crate::x509::{extensions, ocsp, oid};
|
||||
|
|
@ -118,8 +118,8 @@ impl OCSPRequest {
|
|||
&mut self.cached_extensions,
|
||||
&self.raw.borrow_value().tbs_request.request_extensions,
|
||||
|oid, value| {
|
||||
match oid {
|
||||
&oid::NONCE_OID => {
|
||||
match *oid {
|
||||
oid::NONCE_OID => {
|
||||
// This is a disaster. RFC 2560 says that the contents of the nonce is
|
||||
// just the raw extension value. This is nonsense, since they're always
|
||||
// supposed to be ASN.1 TLVs. RFC 6960 correctly specifies that the
|
||||
|
|
@ -129,6 +129,19 @@ impl OCSPRequest {
|
|||
let nonce = asn1::parse_single::<&[u8]>(value).unwrap_or(value);
|
||||
Ok(Some(x509_module.call_method1("OCSPNonce", (nonce,))?))
|
||||
}
|
||||
oid::ACCEPTABLE_RESPONSES_OID => {
|
||||
let oids = asn1::parse_single::<
|
||||
asn1::SequenceOf<'_, asn1::ObjectIdentifier>,
|
||||
>(value)?;
|
||||
let py_oids = pyo3::types::PyList::empty(py);
|
||||
for oid in oids {
|
||||
py_oids.append(oid_to_py_oid(py, &oid)?)?;
|
||||
}
|
||||
|
||||
Ok(Some(
|
||||
x509_module.call_method1("OCSPAcceptableResponses", (py_oids,))?,
|
||||
))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ pub(crate) const POLICY_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2,
|
|||
pub(crate) const EXTENDED_KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37);
|
||||
pub(crate) const FRESHEST_CRL_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 46);
|
||||
pub(crate) const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 54);
|
||||
pub(crate) const ACCEPTABLE_RESPONSES_OID: asn1::ObjectIdentifier =
|
||||
asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 4);
|
||||
|
||||
// Signing methods
|
||||
pub(crate) const ECDSA_WITH_SHA224_OID: asn1::ObjectIdentifier =
|
||||
|
|
|
|||
|
|
@ -102,6 +102,18 @@ class TestOCSPRequest:
|
|||
b"{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd"
|
||||
)
|
||||
|
||||
def test_load_request_with_acceptable_responses(self):
|
||||
req = _load_data(
|
||||
os.path.join("x509", "ocsp", "req-acceptable-responses.der"),
|
||||
ocsp.load_der_ocsp_request,
|
||||
)
|
||||
assert len(req.extensions) == 1
|
||||
ext = req.extensions[0]
|
||||
assert ext.critical is False
|
||||
assert ext.value == x509.OCSPAcceptableResponses(
|
||||
[x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")]
|
||||
)
|
||||
|
||||
def test_load_request_with_unknown_extension(self):
|
||||
req = _load_data(
|
||||
os.path.join("x509", "ocsp", "req-ext-unknown-oid.der"),
|
||||
|
|
|
|||
|
|
@ -6132,6 +6132,73 @@ class TestOCSPNonce:
|
|||
assert ext.public_bytes() == b"\x04\x0500000"
|
||||
|
||||
|
||||
class TestOCSPAcceptableResponses:
|
||||
def test_invalid_types(self):
|
||||
with pytest.raises(TypeError):
|
||||
x509.OCSPAcceptableResponses(38) # type:ignore[arg-type]
|
||||
with pytest.raises(TypeError):
|
||||
x509.OCSPAcceptableResponses([38]) # type:ignore[list-item]
|
||||
|
||||
def test_eq(self):
|
||||
acceptable_responses1 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.3")]
|
||||
)
|
||||
acceptable_responses2 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.3")]
|
||||
)
|
||||
assert acceptable_responses1 == acceptable_responses2
|
||||
|
||||
def test_ne(self):
|
||||
acceptable_responses1 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.3")]
|
||||
)
|
||||
acceptable_responses2 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.4")]
|
||||
)
|
||||
assert acceptable_responses1 != acceptable_responses2
|
||||
assert acceptable_responses1 != object()
|
||||
|
||||
def test_repr(self):
|
||||
acceptable_responses = x509.OCSPAcceptableResponses([])
|
||||
assert (
|
||||
repr(acceptable_responses)
|
||||
== "<OCSPAcceptableResponses(responses=[])>"
|
||||
)
|
||||
|
||||
def test_hash(self):
|
||||
acceptable_responses1 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.3")]
|
||||
)
|
||||
acceptable_responses2 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.3")]
|
||||
)
|
||||
acceptable_responses3 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.4")]
|
||||
)
|
||||
|
||||
assert hash(acceptable_responses1) == hash(acceptable_responses2)
|
||||
assert hash(acceptable_responses1) != hash(acceptable_responses3)
|
||||
|
||||
def test_iter(self):
|
||||
acceptable_responses1 = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.2.3")]
|
||||
)
|
||||
|
||||
assert list(acceptable_responses1) == [ObjectIdentifier("1.2.3")]
|
||||
|
||||
def test_public_bytes(self):
|
||||
ext = x509.OCSPAcceptableResponses([])
|
||||
assert ext.public_bytes() == b"\x30\x00"
|
||||
|
||||
ext = x509.OCSPAcceptableResponses(
|
||||
[ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")]
|
||||
)
|
||||
assert (
|
||||
ext.public_bytes()
|
||||
== b"\x30\x0b\x06\t+\x06\x01\x05\x05\x07\x30\x01\x01"
|
||||
)
|
||||
|
||||
|
||||
def test_all_extension_oid_members_have_names_defined():
|
||||
for oid in dir(ExtensionOID):
|
||||
if oid.startswith("__"):
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in a new issue