diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2b33d8040ac5..62a1da2fce60 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -26,6 +26,9 @@ Changelog empty ``GeneralNames`` sequence, matching the ``SIZE (1..MAX)`` constraint RFC 5280 4.2.1.6/4.2.1.7 places on the field and the strictness already applied to ``extendedKeyUsage``. +* Added the :meth:`PolicyBuilder.extended_key_usage + ` method to + configure the Extended Key Usage checked by X.509 verification policies. .. _v50-0-2: diff --git a/docs/x509/verification.rst b/docs/x509/verification.rst index e7e144252d0f..c1d6e5ae7293 100644 --- a/docs/x509/verification.rst +++ b/docs/x509/verification.rst @@ -259,6 +259,23 @@ the root of trust: :returns: A new instance of :class:`PolicyBuilder` + .. method:: extended_key_usage(extended_key_usage) + + Sets the Extended Key Usage OID used for checking certificates. + Under the default extension policies, an end-entity certificate may + omit the EKU extension; if present, it must include this OID. + If this method is not called, client and server verifiers require + ``clientAuth`` and ``serverAuth``, respectively. + + The configured OID is also checked when a CA certificate includes an + EKU extension. + + :param extended_key_usage: An + :class:`~cryptography.x509.ObjectIdentifier` for the required + Extended Key Usage + + :returns: A new instance of :class:`PolicyBuilder` + .. method:: extension_policies(*, ee_policy, ca_policy) .. versionadded:: 45.0.0 diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi index 4438cc77778c..53eb234a57fb 100644 --- a/src/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -215,6 +215,9 @@ class PolicyBuilder: def time(self, time: datetime.datetime) -> PolicyBuilder: ... def store(self, store: Store) -> PolicyBuilder: ... def max_chain_depth(self, max_chain_depth: int) -> PolicyBuilder: ... + def extended_key_usage( + self, extended_key_usage: x509.ObjectIdentifier + ) -> PolicyBuilder: ... def extension_policies( self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy ) -> PolicyBuilder: ... diff --git a/src/rust/cryptography-x509-verification/src/policy/mod.rs b/src/rust/cryptography-x509-verification/src/policy/mod.rs index 15a51c6412ac..6f4c75814a01 100644 --- a/src/rust/cryptography-x509-verification/src/policy/mod.rs +++ b/src/rust/cryptography-x509-verification/src/policy/mod.rs @@ -318,6 +318,12 @@ impl<'a, B: CryptoOps + 'a> PolicyDefinition<'a, B> { Ok(retval) } + /// Set the Extended Key Usage required by this policy. + pub fn with_extended_key_usage(mut self, extended_key_usage: ObjectIdentifier) -> Self { + self.extended_key_usage = extended_key_usage; + self + } + /// Create a new policy with suitable defaults for client certification /// validation. /// diff --git a/src/rust/src/x509/verify/mod.rs b/src/rust/src/x509/verify/mod.rs index 3fce53d6a4b4..a9da8108ed95 100644 --- a/src/rust/src/x509/verify/mod.rs +++ b/src/rust/src/x509/verify/mod.rs @@ -17,6 +17,7 @@ pub(crate) use extension_policy::{PyCriticality, PyExtensionPolicy}; pub(crate) use policy::PyPolicy; use super::parse_general_names; +use crate::asn1::py_oid_to_oid; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; use crate::types; @@ -85,6 +86,7 @@ pub(crate) struct PolicyBuilder { time: Option, store: Option>, max_chain_depth: Option, + extended_key_usage: Option, ca_ext_policy: Option>, ee_ext_policy: Option>, } @@ -95,6 +97,7 @@ impl PolicyBuilder { time: self.time.clone(), store: self.store.as_ref().map(|s| s.clone_ref(py)), max_chain_depth: self.max_chain_depth, + extended_key_usage: self.extended_key_usage.clone(), ca_ext_policy: self.ca_ext_policy.as_ref().map(|p| p.clone_ref(py)), ee_ext_policy: self.ee_ext_policy.as_ref().map(|p| p.clone_ref(py)), } @@ -109,6 +112,7 @@ impl PolicyBuilder { time: None, store: None, max_chain_depth: None, + extended_key_usage: None, ca_ext_policy: None, ee_ext_policy: None, } @@ -153,6 +157,19 @@ impl PolicyBuilder { }) } + fn extended_key_usage( + &self, + py: pyo3::Python<'_>, + extended_key_usage: pyo3::Bound<'_, pyo3::PyAny>, + ) -> CryptographyResult { + policy_builder_set_once_check!(self, extended_key_usage, "extended key usage"); + + Ok(PolicyBuilder { + extended_key_usage: Some(py_oid_to_oid(extended_key_usage)?), + ..self.py_clone(py) + }) + } + #[pyo3(signature = (*, ca_policy, ee_policy))] fn extension_policies( &self, @@ -199,6 +216,13 @@ impl PolicyBuilder { .as_ref() .map(|p| p.get().clone_inner_policy()), ) + .map(|policy_definition| { + if let Some(extended_key_usage) = &self.extended_key_usage { + policy_definition.with_extended_key_usage(extended_key_usage.clone()) + } else { + policy_definition + } + }) .map_err(pyo3::exceptions::PyValueError::new_err) })?; @@ -256,6 +280,13 @@ impl PolicyBuilder { .as_ref() .map(|p| p.get().clone_inner_policy()), ) + .map(|policy_definition| { + if let Some(extended_key_usage) = &self.extended_key_usage { + policy_definition.with_extended_key_usage(extended_key_usage.clone()) + } else { + policy_definition + } + }) .map_err(pyo3::exceptions::PyValueError::new_err) })?; diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index 696ab60076ff..60fec73734c9 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -55,6 +55,35 @@ def test_max_chain_depth_already_set(self): with pytest.raises(ValueError): PolicyBuilder().max_chain_depth(8).max_chain_depth(9) + def test_extended_key_usage_already_set(self): + builder = PolicyBuilder().extended_key_usage( + ExtendedKeyUsageOID.CODE_SIGNING + ) + with pytest.raises(ValueError): + builder.extended_key_usage(ExtendedKeyUsageOID.CODE_SIGNING) + + def test_extended_key_usage_bad_type(self): + with pytest.raises(TypeError): + PolicyBuilder().extended_key_usage( + typing.cast(typing.Any, "not an object identifier") + ) + + def test_extended_key_usage(self): + extended_key_usage = ExtendedKeyUsageOID.CODE_SIGNING + builder = ( + PolicyBuilder() + .store(dummy_store()) + .extended_key_usage(extended_key_usage) + ) + + server_verifier = builder.build_server_verifier( + DNSName("cryptography.io") + ) + client_verifier = builder.build_client_verifier() + + assert server_verifier.policy.extended_key_usage == extended_key_usage + assert client_verifier.policy.extended_key_usage == extended_key_usage + def test_ipaddress_subject(self): verifier = ( PolicyBuilder()