From 37378e9d7b35f5dca4655bc97d9713badb2b627f Mon Sep 17 00:00:00 2001 From: Arpit Kumar Jaiswal Date: Fri, 16 Oct 2026 03:27:26 +0530 Subject: [PATCH 1/5] Add configurable EKU to X.509 verification policies --- CHANGELOG.rst | 3 ++ docs/x509/verification.rst | 17 +++++++++ .../src/policy/mod.rs | 6 ++++ src/rust/src/x509/verify/mod.rs | 35 ++++++++++++++++--- 4 files changed, 57 insertions(+), 4 deletions(-) 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..934b43e7bd4a 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 checked by the default validators. + 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/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..df8f30829729 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, @@ -188,7 +205,7 @@ impl PolicyBuilder { }; let policy_definition = OwnedPolicyDefinition::try_new(None, |_subject| { - PolicyDefinition::client( + let mut policy_definition = PolicyDefinition::client( PyCryptoOps {}, time, self.max_chain_depth, @@ -199,7 +216,12 @@ impl PolicyBuilder { .as_ref() .map(|p| p.get().clone_inner_policy()), ) - .map_err(pyo3::exceptions::PyValueError::new_err) + .map_err(pyo3::exceptions::PyValueError::new_err)?; + if let Some(extended_key_usage) = &self.extended_key_usage { + policy_definition = + policy_definition.with_extended_key_usage(extended_key_usage.clone()); + } + Ok(policy_definition) })?; let py_policy = PyPolicy { @@ -244,7 +266,7 @@ impl PolicyBuilder { .expect("subject_owner for ServerVerifier can not be None"), )?; - PolicyDefinition::server( + let mut policy_definition = PolicyDefinition::server( PyCryptoOps {}, subject, time, @@ -256,7 +278,12 @@ impl PolicyBuilder { .as_ref() .map(|p| p.get().clone_inner_policy()), ) - .map_err(pyo3::exceptions::PyValueError::new_err) + .map_err(pyo3::exceptions::PyValueError::new_err)?; + if let Some(extended_key_usage) = &self.extended_key_usage { + policy_definition = + policy_definition.with_extended_key_usage(extended_key_usage.clone()); + } + Ok(policy_definition) })?; let py_policy = PyPolicy { From 224ecd0df9db51045bfc45ce84cb24e9adb7a682 Mon Sep 17 00:00:00 2001 From: Arpit Kumar Jaiswal Date: Fri, 16 Oct 2026 03:31:35 +0530 Subject: [PATCH 2/5] Fix X.509 verifier closure type inference --- src/rust/src/x509/verify/mod.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/rust/src/x509/verify/mod.rs b/src/rust/src/x509/verify/mod.rs index df8f30829729..a9da8108ed95 100644 --- a/src/rust/src/x509/verify/mod.rs +++ b/src/rust/src/x509/verify/mod.rs @@ -205,7 +205,7 @@ impl PolicyBuilder { }; let policy_definition = OwnedPolicyDefinition::try_new(None, |_subject| { - let mut policy_definition = PolicyDefinition::client( + PolicyDefinition::client( PyCryptoOps {}, time, self.max_chain_depth, @@ -216,12 +216,14 @@ impl PolicyBuilder { .as_ref() .map(|p| p.get().clone_inner_policy()), ) - .map_err(pyo3::exceptions::PyValueError::new_err)?; - if let Some(extended_key_usage) = &self.extended_key_usage { - policy_definition = - policy_definition.with_extended_key_usage(extended_key_usage.clone()); - } - Ok(policy_definition) + .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) })?; let py_policy = PyPolicy { @@ -266,7 +268,7 @@ impl PolicyBuilder { .expect("subject_owner for ServerVerifier can not be None"), )?; - let mut policy_definition = PolicyDefinition::server( + PolicyDefinition::server( PyCryptoOps {}, subject, time, @@ -278,12 +280,14 @@ impl PolicyBuilder { .as_ref() .map(|p| p.get().clone_inner_policy()), ) - .map_err(pyo3::exceptions::PyValueError::new_err)?; - if let Some(extended_key_usage) = &self.extended_key_usage { - policy_definition = - policy_definition.with_extended_key_usage(extended_key_usage.clone()); - } - Ok(policy_definition) + .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) })?; let py_policy = PyPolicy { From 0bbe7e07a61968ed1f24cb155dbabbbfd7c8730c Mon Sep 17 00:00:00 2001 From: Arpit Kumar Jaiswal Date: Fri, 16 Oct 2026 05:06:25 +0530 Subject: [PATCH 3/5] Test configurable X.509 EKU policies --- docs/x509/verification.rst | 2 +- tests/x509/verification/test_verification.py | 27 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/x509/verification.rst b/docs/x509/verification.rst index 934b43e7bd4a..c1d6e5ae7293 100644 --- a/docs/x509/verification.rst +++ b/docs/x509/verification.rst @@ -261,7 +261,7 @@ the root of trust: .. method:: extended_key_usage(extended_key_usage) - Sets the Extended Key Usage OID checked by the default validators. + 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 diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index 696ab60076ff..ca6bc5fd2400 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -55,6 +55,33 @@ 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() From 8d219625eee0c0f8416a377d1a14e961abc68377 Mon Sep 17 00:00:00 2001 From: Arpit Kumar Jaiswal Date: Fri, 16 Oct 2026 05:12:12 +0530 Subject: [PATCH 4/5] Format EKU policy test --- tests/x509/verification/test_verification.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index ca6bc5fd2400..60fec73734c9 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -70,8 +70,10 @@ def test_extended_key_usage_bad_type(self): def test_extended_key_usage(self): extended_key_usage = ExtendedKeyUsageOID.CODE_SIGNING - builder = PolicyBuilder().store(dummy_store()).extended_key_usage( - extended_key_usage + builder = ( + PolicyBuilder() + .store(dummy_store()) + .extended_key_usage(extended_key_usage) ) server_verifier = builder.build_server_verifier( From 874f94b522def7ff6de3c20d8ac121f4b5ab8b97 Mon Sep 17 00:00:00 2001 From: Arpit Kumar Jaiswal Date: Fri, 16 Oct 2026 05:18:50 +0530 Subject: [PATCH 5/5] Add EKU policy builder type stub --- src/cryptography/hazmat/bindings/_rust/x509.pyi | 3 +++ 1 file changed, 3 insertions(+) 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: ...