From fafb1574501be2b2d461dadfb9af7dc338436d60 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:07:02 +0200 Subject: [PATCH 1/9] windows: shorten URL retrieval timeout some more --- rustls-platform-verifier/src/verification/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index 6b71c6f..bf8c50c 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -466,7 +466,7 @@ impl CertificateStore { | CERT_CHAIN_CACHE_END_CERT; // Lowering URL retrieval timeout from default 15s to 10s to account for higher internet speeds - parameters.dwUrlRetrievalTimeout = 10 * 1000; // milliseconds + parameters.dwUrlRetrievalTimeout = 3 * 1000; // milliseconds // SAFETY: `cert` points to a valid certificate context, parameters is valid for reads, `cert_chain` is valid // for writes, and the certificate store is valid and initialized. From 3ba891f936b48a9c1d6500c647729c249f8948f5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:11:04 +0200 Subject: [PATCH 2/9] windows: extract imports --- rustls-platform-verifier/src/verification/windows.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index bf8c50c..a34f4cf 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -33,6 +33,10 @@ use rustls::{ CertificateError, DigitallySignedStruct, Error as TlsError, Error::InvalidCertificate, SignatureScheme, }; +#[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] +use windows_sys::Win32::Security::Cryptography::{ + CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL, CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE, +}; use windows_sys::Win32::{ Foundation::{ CERT_E_CN_NO_MATCH, CERT_E_EXPIRED, CERT_E_INVALID_NAME, CERT_E_UNTRUSTEDROOT, @@ -273,10 +277,6 @@ impl CertEngine { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] fn new_with_fake_root(root: &[u8]) -> Result { - use windows_sys::Win32::Security::Cryptography::{ - CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL, CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE, - }; - let mut root_store = CertificateStore::new()?; root_store.add_cert(root)?; From 3f7fd668923e210edbf7c9c139418bf6a216f61f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:19:23 +0200 Subject: [PATCH 3/9] windows: re-order items for top-down order --- .../src/verification/windows.rs | 810 +++++++++--------- 1 file changed, 404 insertions(+), 406 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index a34f4cf..c65066a 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -59,185 +59,235 @@ use windows_sys::Win32::{ }, }; -use super::{log_server_cert, ALLOWED_EKUS}; +use super::{invalid_certificate, log_server_cert, ALLOWED_EKUS}; -// The `windows-sys` definition for `CERT_CHAIN_PARA` does not take old OS versions -// into account so we define it ourselves for better OS backwards compat. -// In the future a compile-time size assertion can be added against the upstream type to help stay in sync. -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -struct CERT_CHAIN_PARA { - pub cbSize: u32, - pub RequestedUsage: CERT_USAGE_MATCH, - pub RequestedIssuancePolicy: CERT_USAGE_MATCH, - pub dwUrlRetrievalTimeout: u32, - pub fCheckRevocationFreshnessTime: i32, // BOOL - pub dwRevocationFreshnessTime: u32, - pub pftCacheResync: *mut FILETIME, - // XXX: `pStrongSignPara` and `dwStrongSignFlags` might or might not be defined on the current system. It started - // being available in Windows 8. See https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_para - #[cfg(not(target_vendor = "win7"))] - pub pStrongSignPara: *const CERT_STRONG_SIGN_PARA, - #[cfg(not(target_vendor = "win7"))] - pub dwStrongSignFlags: u32, -} - -// Same workaround with CERT_CHAIN_PARA -#[allow(non_camel_case_types, non_snake_case)] -#[repr(C)] -#[derive(Clone, Copy)] -pub struct CERT_CHAIN_ENGINE_CONFIG { - pub cbSize: u32, - pub hRestrictedRoot: HCERTSTORE, - pub hRestrictedTrust: HCERTSTORE, - pub hRestrictedOther: HCERTSTORE, - pub cAdditionalStore: u32, - pub rghAdditionalStore: *mut HCERTSTORE, - pub dwFlags: u32, - pub dwUrlRetrievalTimeout: u32, - pub MaximumCachedCertificates: u32, - pub CycleDetectionModulus: u32, - pub hExclusiveRoot: HCERTSTORE, - pub hExclusiveTrustedPeople: HCERTSTORE, - // XXX: `dwExclusiveFlags` started being available in Windows 8 and Windows Server 2012 - // See https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_engine_config - #[cfg(not(target_vendor = "win7"))] - pub dwExclusiveFlags: u32, +/// A TLS certificate verifier that utilizes the Windows certificate facilities. +#[derive(Debug)] +pub struct Verifier { + /// Testing only: The root CA certificate to trust. + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + test_only_root_ca_override: Option>, + crypto_provider: Arc, + /// Extra trust anchors to add to the verifier above and beyond those provided by + /// the system-provided trust stores. + extra_roots: Option, } -use crate::verification::invalid_certificate; - -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for CERT_CHAIN_PARA { - fn zeroed_with_size() -> Self { - // SAFETY: `CERT_CHAIN_PARA` only contains pointers and integers, which are safe to zero. - // Additionally, MSDN states you *MUST* zero all unused fields. - let mut new: Self = unsafe { mem::zeroed() }; - new.cbSize = Self::SIZE; - new +impl Verifier { + /// Creates a new instance of a TLS certificate verifier that utilizes the + /// Windows certificate facilities. + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new(crypto_provider: Arc) -> Result { + Ok(Self { + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + test_only_root_ca_override: None, + crypto_provider, + extra_roots: None, + }) } -} -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for HTTPSPolicyCallbackData { - fn zeroed_with_size() -> Self { - // SAFETY: zeroed is needed here since it contains a union. - let mut new: Self = unsafe { mem::zeroed() }; - new.Anonymous.cbSize = Self::SIZE; - new + /// Creates a new instance of a TLS certificate verifier that utilizes the + /// Windows certificate facilities and augmented by the provided extra root certificates. + #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] + pub fn new_with_extra_roots( + roots: impl IntoIterator>, + crypto_provider: Arc, + ) -> Result { + let cert_engine = CertEngine::new_with_extra_roots(roots)?; + Ok(Self { + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + test_only_root_ca_override: None, + crypto_provider, + extra_roots: Some(cert_engine), + }) } -} -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for CERT_CHAIN_POLICY_PARA { - fn zeroed_with_size() -> Self { - // SAFETY: This structure only contains integers and pointers. - let mut new: Self = unsafe { mem::zeroed() }; - new.cbSize = Self::SIZE; - new + /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + pub(crate) fn new_with_fake_root( + root: pki_types::CertificateDer<'static>, + crypto_provider: Arc, + ) -> Self { + Self { + test_only_root_ca_override: Some(root), + crypto_provider, + extra_roots: None, + } } -} -// SAFETY: see method implementation -unsafe impl ZeroedWithSize for CERT_CHAIN_ENGINE_CONFIG { - fn zeroed_with_size() -> Self { - // SAFETY: This structure only contains integers and pointers. - let mut new: Self = unsafe { mem::zeroed() }; - new.cbSize = Self::SIZE; - new - } -} + /// Verifies a certificate and its chain for the specified `server`. + /// + /// Return `Ok(())` if the certificate was valid. + fn verify_certificate( + &self, + primary_cert: &[u8], + intermediate_certs: &[&[u8]], + server: &[u8], + ocsp_data: Option<&[u8]>, + now: pki_types::UnixTime, + ) -> Result<(), TlsError> { + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + let mut store = match self.test_only_root_ca_override.as_ref() { + Some(test_only_root_ca_override) => { + CertificateStore::new_with_fake_root(test_only_root_ca_override)? + } + None => CertificateStore::new()?, + }; -struct CertChain { - inner: NonNull, -} + #[cfg(not(any(test, feature = "ffi-testing", feature = "dbg")))] + let mut store = CertificateStore::new()?; -impl CertChain { - fn verify_chain_policy( - &self, - mut server_null_terminated: Vec, - ) -> Result { - let mut extra_params = HTTPSPolicyCallbackData::zeroed_with_size(); - extra_params.dwAuthType = AUTHTYPE_SERVER; - // `server_null_terminated` outlives `extra_params`. - extra_params.pwszServerName = server_null_terminated.as_mut_ptr(); + let mut primary_cert = store.add_cert(primary_cert)?; - let mut params = CERT_CHAIN_POLICY_PARA::zeroed_with_size(); - // Ignore any errors when trying to obtain OCSP revocation information. - // This is also done in OpenSSL, Secure Transport from Apple, etc. - params.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; - // `extra_params` outlives `params`. - params.pvExtraPolicyPara = NonNull::from(&mut extra_params).cast::().as_ptr(); + for cert in intermediate_certs.iter().copied() { + store.add_cert(cert)?; + } - let mut status: MaybeUninit = MaybeUninit::uninit(); + if let Some(ocsp_data) = ocsp_data { + #[allow(clippy::as_conversions)] + let data = CRYPT_INTEGER_BLOB { + cbData: ocsp_data.len().try_into().map_err(|_| { + invalid_certificate("Malformed OCSP response stapled to server certificate") + })?, + pbData: ocsp_data.as_ptr() as *mut u8, + }; - // SAFETY: The certificate chain is non-null, `params` is valid for reads, and its valid to write to `status`. - let res = unsafe { - CertVerifyCertificateChainPolicy( - CERT_CHAIN_POLICY_SSL, - self.inner.as_ptr(), - ¶ms, - status.as_mut_ptr(), - ) - }; + // SAFETY: `data` is a valid pointer and matches the property ID. + unsafe { + primary_cert.set_property( + CERT_OCSP_RESPONSE_PROP_ID, + NonNull::from(&data).cast::().as_ptr(), + )?; + } + } - // This should rarely, if ever, be false since it would imply no TLS verification - // is currently possible on the system: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certverifycertificatechainpolicy#return-value - if res != TRUE { - return Err(TlsError::General(String::from( - "TLS certificate verification was unavailable on the system!", - ))); + // Encode UTF-16, null-terminated + let server: Vec = server + .iter() + .map(|c| u16::from(*c)) + .chain(Some(0)) + .collect(); + + let mut cert_chain = store.new_chain_in(&primary_cert, now, store.engine.as_ref())?; + + // We only use `TrustStatus` here because it hasn't had verification performed on it. + // SAFETY: The pointer is guaranteed to be non-null. + let cert_error_status = unsafe { *cert_chain.inner.as_ptr() } + .TrustStatus + .dwErrorStatus; + + let extra_roots_may_needed = + (cert_error_status & (CERT_TRUST_IS_PARTIAL_CHAIN | CERT_TRUST_IS_UNTRUSTED_ROOT)) != 0; + + // If we have extra roots and building the chain gave us an error, we try to build a + // new one with the extra roots. + if extra_roots_may_needed && self.extra_roots.is_some() { + let mut store = CertificateStore::new()?; + + for cert in intermediate_certs.iter().copied() { + store.add_cert(cert)?; + } + + cert_chain = store.new_chain_in(&primary_cert, now, self.extra_roots.as_ref())?; } - // SAFETY: The verification call was checked to have succeeded, so the status - // is written correctly and initialized. - let status = unsafe { status.assume_init() }; - Ok(status) + let status = cert_chain.verify_chain_policy(server)?; + + if status.dwError == 0 { + return Ok(()); + } + + // Only map the errors we have tests for. + #[allow(clippy::as_conversions)] + let win_error = status.dwError as i32; + Err(match win_error { + CERT_E_CN_NO_MATCH | CERT_E_INVALID_NAME => { + InvalidCertificate(CertificateError::NotValidForName) + } + CRYPT_E_REVOKED => InvalidCertificate(CertificateError::Revoked), + CERT_E_EXPIRED => InvalidCertificate(CertificateError::Expired), + CERT_E_UNTRUSTEDROOT => InvalidCertificate(CertificateError::UnknownIssuer), + CERT_E_WRONG_USAGE => InvalidCertificate(CertificateError::InvalidPurpose), + error_num => { + let err = std::io::Error::from_raw_os_error(error_num); + // The included error message has both the description and raw OS error code. + invalid_certificate(err.to_string()) + } + }) } } -impl Drop for CertChain { - fn drop(&mut self) { - // SAFETY: The pointer is guaranteed to be non-null. - unsafe { CertFreeCertificateChain(self.inner.as_ptr()) } +#[cfg_attr(docsrs, doc(cfg(all())))] +impl ServerCertVerifier for Verifier { + fn verify_server_cert( + &self, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &pki_types::ServerName, + ocsp_response: &[u8], + now: pki_types::UnixTime, + ) -> Result { + log_server_cert(end_entity); + + let name = server_name.to_str(); + + let intermediate_certs: Vec<&[u8]> = intermediates.iter().map(|c| c.as_ref()).collect(); + + let ocsp_data = if !ocsp_response.is_empty() { + Some(ocsp_response) + } else { + None + }; + + match self.verify_certificate( + end_entity.as_ref(), + &intermediate_certs, + name.as_bytes(), + ocsp_data, + now, + ) { + Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), + Err(e) => { + // SAFETY: + // Errors are our own custom errors, WinAPI errors, or static strings. + log::error!("failed to verify TLS certificate: {}", e); + Err(e) + } + } } -} -/// A representation of a certificate. -/// -/// The `CertificateStore` must be opened with the correct flags to ensure the -/// certificate may outlive it; see the `CertificateStore` documentation. -struct Certificate { - inner: NonNull, -} + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &self.crypto_provider.signature_verification_algorithms, + ) + } -impl Certificate { - /// Sets the specified property of this certificate context. - /// - /// ### Safety - /// `prop_data` must be a valid pointer for the property type. - unsafe fn set_property( - &mut self, - prop_id: u32, - prop_data: *const c_void, - ) -> Result<(), TlsError> { - // SAFETY: `cert` points to a valid certificate context and the OCSP data is valid to read. - call_with_last_error(|| { - (CertSetCertificateContextProperty( - self.inner.as_ptr(), - prop_id, - CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, - prop_data, - ) == TRUE) - .then_some(()) - }) + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &self.crypto_provider.signature_verification_algorithms, + ) } -} -impl Drop for Certificate { - fn drop(&mut self) { - // SAFETY: The certificate context is non-null and points to a valid location. - unsafe { CertFreeCertificateContext(self.inner.as_ptr()) }; + fn supported_verify_schemes(&self) -> Vec { + self.crypto_provider + .signature_verification_algorithms + .supported_schemes() } } @@ -339,14 +389,20 @@ struct CertificateStore { engine: Option, // HCERTENGINECONTEXT } -impl Drop for CertificateStore { - fn drop(&mut self) { - // SAFETY: See the `CertificateStore` documentation. - unsafe { CertCloseStore(self.inner.as_ptr(), 0) }; +impl CertificateStore { + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + fn new_with_fake_root(root: &[u8]) -> Result { + let mut inner = Self::new()?; + + let mut root_store = Self::new()?; + root_store.add_cert(root)?; + + let engine = CertEngine::new_with_fake_root(root)?; + inner.engine = Some(engine); + + Ok(inner) } -} -impl CertificateStore { /// Creates a new, in-memory certificate store. fn new() -> Result { let store = call_with_last_error(|| { @@ -371,19 +427,6 @@ impl CertificateStore { }) } - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: &[u8]) -> Result { - let mut inner = Self::new()?; - - let mut root_store = CertificateStore::new()?; - root_store.add_cert(root)?; - - let engine = CertEngine::new_with_fake_root(root)?; - inner.engine = Some(engine); - - Ok(inner) - } - /// Adds the provided certificate to the store. /// /// The certificate must be encoded as ASN.1 DER. @@ -501,270 +544,215 @@ impl CertificateStore { } } -// `windows-sys` >= 0.60 -impl EnginePtr for *mut c_void { - fn from_raw(val: NonNull) -> Self { - val.as_ptr() - } - - const NULL: Self = ptr::null_mut(); -} - -// `windows-sys` 0.52-0.59 -impl EnginePtr for isize { - #[allow(clippy::as_conversions)] - fn from_raw(val: NonNull) -> Self { - val.as_ptr() as isize - } - - const NULL: Self = 0; -} - -/// An abstraction trait over the different ways various `windows-sys` versions represent -/// the type of `HCERTCHAINENGINE`. -trait EnginePtr: Sized { - fn from_raw(val: NonNull) -> Self; - - const NULL: Self; -} - -fn call_with_last_error Option>(mut call: F) -> Result { - if let Some(res) = call() { - Ok(res) - } else { - Err(TlsError::General( - std::io::Error::last_os_error().to_string(), - )) +impl Drop for CertificateStore { + fn drop(&mut self) { + // SAFETY: See the `CertificateStore` documentation. + unsafe { CertCloseStore(self.inner.as_ptr(), 0) }; } } -/// A TLS certificate verifier that utilizes the Windows certificate facilities. -#[derive(Debug)] -pub struct Verifier { - /// Testing only: The root CA certificate to trust. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: Option>, - crypto_provider: Arc, - /// Extra trust anchors to add to the verifier above and beyond those provided by - /// the system-provided trust stores. - extra_roots: Option, +struct CertChain { + inner: NonNull, } -impl Verifier { - /// Creates a new instance of a TLS certificate verifier that utilizes the - /// Windows certificate facilities. - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new(crypto_provider: Arc) -> Result { - Ok(Self { - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: None, - crypto_provider, - extra_roots: None, - }) - } - - /// Creates a new instance of a TLS certificate verifier that utilizes the - /// Windows certificate facilities and augmented by the provided extra root certificates. - #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] - pub fn new_with_extra_roots( - roots: impl IntoIterator>, - crypto_provider: Arc, - ) -> Result { - let cert_engine = CertEngine::new_with_extra_roots(roots)?; - Ok(Self { - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - test_only_root_ca_override: None, - crypto_provider, - extra_roots: Some(cert_engine), - }) - } - - /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - pub(crate) fn new_with_fake_root( - root: pki_types::CertificateDer<'static>, - crypto_provider: Arc, - ) -> Self { - Self { - test_only_root_ca_override: Some(root), - crypto_provider, - extra_roots: None, - } - } - - /// Verifies a certificate and its chain for the specified `server`. - /// - /// Return `Ok(())` if the certificate was valid. - fn verify_certificate( +impl CertChain { + fn verify_chain_policy( &self, - primary_cert: &[u8], - intermediate_certs: &[&[u8]], - server: &[u8], - ocsp_data: Option<&[u8]>, - now: pki_types::UnixTime, - ) -> Result<(), TlsError> { - #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - let mut store = match self.test_only_root_ca_override.as_ref() { - Some(test_only_root_ca_override) => { - CertificateStore::new_with_fake_root(test_only_root_ca_override)? - } - None => CertificateStore::new()?, - }; - - #[cfg(not(any(test, feature = "ffi-testing", feature = "dbg")))] - let mut store = CertificateStore::new()?; - - let mut primary_cert = store.add_cert(primary_cert)?; + mut server_null_terminated: Vec, + ) -> Result { + let mut extra_params = HTTPSPolicyCallbackData::zeroed_with_size(); + extra_params.dwAuthType = AUTHTYPE_SERVER; + // `server_null_terminated` outlives `extra_params`. + extra_params.pwszServerName = server_null_terminated.as_mut_ptr(); - for cert in intermediate_certs.iter().copied() { - store.add_cert(cert)?; - } + let mut params = CERT_CHAIN_POLICY_PARA::zeroed_with_size(); + // Ignore any errors when trying to obtain OCSP revocation information. + // This is also done in OpenSSL, Secure Transport from Apple, etc. + params.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; + // `extra_params` outlives `params`. + params.pvExtraPolicyPara = NonNull::from(&mut extra_params).cast::().as_ptr(); - if let Some(ocsp_data) = ocsp_data { - #[allow(clippy::as_conversions)] - let data = CRYPT_INTEGER_BLOB { - cbData: ocsp_data.len().try_into().map_err(|_| { - invalid_certificate("Malformed OCSP response stapled to server certificate") - })?, - pbData: ocsp_data.as_ptr() as *mut u8, - }; + let mut status: MaybeUninit = MaybeUninit::uninit(); - // SAFETY: `data` is a valid pointer and matches the property ID. - unsafe { - primary_cert.set_property( - CERT_OCSP_RESPONSE_PROP_ID, - NonNull::from(&data).cast::().as_ptr(), - )?; - } - } + // SAFETY: The certificate chain is non-null, `params` is valid for reads, and its valid to write to `status`. + let res = unsafe { + CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + self.inner.as_ptr(), + ¶ms, + status.as_mut_ptr(), + ) + }; - // Encode UTF-16, null-terminated - let server: Vec = server - .iter() - .map(|c| u16::from(*c)) - .chain(Some(0)) - .collect(); + // This should rarely, if ever, be false since it would imply no TLS verification + // is currently possible on the system: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certverifycertificatechainpolicy#return-value + if res != TRUE { + return Err(TlsError::General(String::from( + "TLS certificate verification was unavailable on the system!", + ))); + } - let mut cert_chain = store.new_chain_in(&primary_cert, now, store.engine.as_ref())?; + // SAFETY: The verification call was checked to have succeeded, so the status + // is written correctly and initialized. + let status = unsafe { status.assume_init() }; + Ok(status) + } +} - // We only use `TrustStatus` here because it hasn't had verification performed on it. +impl Drop for CertChain { + fn drop(&mut self) { // SAFETY: The pointer is guaranteed to be non-null. - let cert_error_status = unsafe { *cert_chain.inner.as_ptr() } - .TrustStatus - .dwErrorStatus; + unsafe { CertFreeCertificateChain(self.inner.as_ptr()) } + } +} - let extra_roots_may_needed = - (cert_error_status & (CERT_TRUST_IS_PARTIAL_CHAIN | CERT_TRUST_IS_UNTRUSTED_ROOT)) != 0; +/// A representation of a certificate. +/// +/// The `CertificateStore` must be opened with the correct flags to ensure the +/// certificate may outlive it; see the `CertificateStore` documentation. +struct Certificate { + inner: NonNull, +} - // If we have extra roots and building the chain gave us an error, we try to build a - // new one with the extra roots. - if extra_roots_may_needed && self.extra_roots.is_some() { - let mut store = CertificateStore::new()?; +impl Certificate { + /// Sets the specified property of this certificate context. + /// + /// ### Safety + /// `prop_data` must be a valid pointer for the property type. + unsafe fn set_property( + &mut self, + prop_id: u32, + prop_data: *const c_void, + ) -> Result<(), TlsError> { + // SAFETY: `cert` points to a valid certificate context and the OCSP data is valid to read. + call_with_last_error(|| { + (CertSetCertificateContextProperty( + self.inner.as_ptr(), + prop_id, + CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, + prop_data, + ) == TRUE) + .then_some(()) + }) + } +} - for cert in intermediate_certs.iter().copied() { - store.add_cert(cert)?; - } +impl Drop for Certificate { + fn drop(&mut self) { + // SAFETY: The certificate context is non-null and points to a valid location. + unsafe { CertFreeCertificateContext(self.inner.as_ptr()) }; + } +} - cert_chain = store.new_chain_in(&primary_cert, now, self.extra_roots.as_ref())?; - } +// The `windows-sys` definition for `CERT_CHAIN_PARA` does not take old OS versions +// into account so we define it ourselves for better OS backwards compat. +// In the future a compile-time size assertion can be added against the upstream type to help stay in sync. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +struct CERT_CHAIN_PARA { + pub cbSize: u32, + pub RequestedUsage: CERT_USAGE_MATCH, + pub RequestedIssuancePolicy: CERT_USAGE_MATCH, + pub dwUrlRetrievalTimeout: u32, + pub fCheckRevocationFreshnessTime: i32, // BOOL + pub dwRevocationFreshnessTime: u32, + pub pftCacheResync: *mut FILETIME, + // XXX: `pStrongSignPara` and `dwStrongSignFlags` might or might not be defined on the current system. It started + // being available in Windows 8. See https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_para + #[cfg(not(target_vendor = "win7"))] + pub pStrongSignPara: *const CERT_STRONG_SIGN_PARA, + #[cfg(not(target_vendor = "win7"))] + pub dwStrongSignFlags: u32, +} - let status = cert_chain.verify_chain_policy(server)?; +// SAFETY: see method implementation +unsafe impl ZeroedWithSize for CERT_CHAIN_PARA { + fn zeroed_with_size() -> Self { + // SAFETY: `CERT_CHAIN_PARA` only contains pointers and integers, which are safe to zero. + // Additionally, MSDN states you *MUST* zero all unused fields. + let mut new: Self = unsafe { mem::zeroed() }; + new.cbSize = Self::SIZE; + new + } +} - if status.dwError == 0 { - return Ok(()); - } +// Same workaround with CERT_CHAIN_PARA +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +#[derive(Clone, Copy)] +pub struct CERT_CHAIN_ENGINE_CONFIG { + pub cbSize: u32, + pub hRestrictedRoot: HCERTSTORE, + pub hRestrictedTrust: HCERTSTORE, + pub hRestrictedOther: HCERTSTORE, + pub cAdditionalStore: u32, + pub rghAdditionalStore: *mut HCERTSTORE, + pub dwFlags: u32, + pub dwUrlRetrievalTimeout: u32, + pub MaximumCachedCertificates: u32, + pub CycleDetectionModulus: u32, + pub hExclusiveRoot: HCERTSTORE, + pub hExclusiveTrustedPeople: HCERTSTORE, + // XXX: `dwExclusiveFlags` started being available in Windows 8 and Windows Server 2012 + // See https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_engine_config + #[cfg(not(target_vendor = "win7"))] + pub dwExclusiveFlags: u32, +} - // Only map the errors we have tests for. - #[allow(clippy::as_conversions)] - let win_error = status.dwError as i32; - Err(match win_error { - CERT_E_CN_NO_MATCH | CERT_E_INVALID_NAME => { - InvalidCertificate(CertificateError::NotValidForName) - } - CRYPT_E_REVOKED => InvalidCertificate(CertificateError::Revoked), - CERT_E_EXPIRED => InvalidCertificate(CertificateError::Expired), - CERT_E_UNTRUSTEDROOT => InvalidCertificate(CertificateError::UnknownIssuer), - CERT_E_WRONG_USAGE => InvalidCertificate(CertificateError::InvalidPurpose), - error_num => { - let err = std::io::Error::from_raw_os_error(error_num); - // The included error message has both the description and raw OS error code. - invalid_certificate(err.to_string()) - } - }) +// `windows-sys` >= 0.60 +impl EnginePtr for *mut c_void { + fn from_raw(val: NonNull) -> Self { + val.as_ptr() } -} -#[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( - &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); + const NULL: Self = ptr::null_mut(); +} - let name = server_name.to_str(); +// `windows-sys` 0.52-0.59 +impl EnginePtr for isize { + #[allow(clippy::as_conversions)] + fn from_raw(val: NonNull) -> Self { + val.as_ptr() as isize + } - let intermediate_certs: Vec<&[u8]> = intermediates.iter().map(|c| c.as_ref()).collect(); + const NULL: Self = 0; +} - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None - }; +/// An abstraction trait over the different ways various `windows-sys` versions represent +/// the type of `HCERTCHAINENGINE`. +trait EnginePtr: Sized { + fn from_raw(val: NonNull) -> Self; - match self.verify_certificate( - end_entity.as_ref(), - &intermediate_certs, - name.as_bytes(), - ocsp_data, - now, - ) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), - Err(e) => { - // SAFETY: - // Errors are our own custom errors, WinAPI errors, or static strings. - log::error!("failed to verify TLS certificate: {}", e); - Err(e) - } - } - } + const NULL: Self; +} - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls12_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) +// SAFETY: see method implementation +unsafe impl ZeroedWithSize for HTTPSPolicyCallbackData { + fn zeroed_with_size() -> Self { + // SAFETY: zeroed is needed here since it contains a union. + let mut new: Self = unsafe { mem::zeroed() }; + new.Anonymous.cbSize = Self::SIZE; + new } +} - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) +// SAFETY: see method implementation +unsafe impl ZeroedWithSize for CERT_CHAIN_POLICY_PARA { + fn zeroed_with_size() -> Self { + // SAFETY: This structure only contains integers and pointers. + let mut new: Self = unsafe { mem::zeroed() }; + new.cbSize = Self::SIZE; + new } +} - fn supported_verify_schemes(&self) -> Vec { - self.crypto_provider - .signature_verification_algorithms - .supported_schemes() +// SAFETY: see method implementation +unsafe impl ZeroedWithSize for CERT_CHAIN_ENGINE_CONFIG { + fn zeroed_with_size() -> Self { + // SAFETY: This structure only contains integers and pointers. + let mut new: Self = unsafe { mem::zeroed() }; + new.cbSize = Self::SIZE; + new } } @@ -790,3 +778,13 @@ unsafe trait ZeroedWithSize: Sized { /// Returns a zeroed structure with its structure size (`cbSize`) field set to the correct value. fn zeroed_with_size() -> Self; } + +fn call_with_last_error Option>(mut call: F) -> Result { + if let Some(res) = call() { + Ok(res) + } else { + Err(TlsError::General( + std::io::Error::last_os_error().to_string(), + )) + } +} From 7f657b58f4783158b89b92ad25524e4c51045ce3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:24:38 +0200 Subject: [PATCH 4/9] windows: take CertificateDer as a fake root --- rustls-platform-verifier/src/verification/windows.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index c65066a..ae6aa3b 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -129,7 +129,7 @@ impl Verifier { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] let mut store = match self.test_only_root_ca_override.as_ref() { Some(test_only_root_ca_override) => { - CertificateStore::new_with_fake_root(test_only_root_ca_override)? + CertificateStore::new_with_fake_root(test_only_root_ca_override.clone())? } None => CertificateStore::new()?, }; @@ -326,9 +326,9 @@ impl CertEngine { } #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: &[u8]) -> Result { + fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result { let mut root_store = CertificateStore::new()?; - root_store.add_cert(root)?; + root_store.add_cert(&root)?; let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); // We use these flags for the following reasons: @@ -391,11 +391,11 @@ struct CertificateStore { impl CertificateStore { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: &[u8]) -> Result { + fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result { let mut inner = Self::new()?; let mut root_store = Self::new()?; - root_store.add_cert(root)?; + root_store.add_cert(&root)?; let engine = CertEngine::new_with_fake_root(root)?; inner.engine = Some(engine); From 28e7d234000bd9df3a98f0ffb0dd63d2b803b776 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:29:42 +0200 Subject: [PATCH 5/9] windows: extract helper CertEngine constructor --- .../src/verification/windows.rs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index ae6aa3b..c12374b 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -300,37 +300,11 @@ impl CertEngine { fn new_with_extra_roots( roots: impl IntoIterator>, ) -> Result { - let mut exclusive_store = CertificateStore::new()?; - for root in roots { - exclusive_store.add_cert(&root)?; - } - - let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); - config.hExclusiveRoot = exclusive_store.inner.as_ptr(); - - let mut engine = EnginePtr::NULL; - - // XXX: Due to the redefinition of `CERT_CHAIN_ENGINE_CONFIG`, we need to do pointer casts - // in order to pass our expanded structure into `CertCreateCertificateChainEngine`. - // See also `CERT_CHAIN_PARA` casting below. - let config = NonNull::from(&config).cast().as_ptr(); - // SAFETY: `engine` is valid to be written to and the config is valid to be read. - let res = unsafe { CertCreateCertificateChainEngine(config, &mut engine) }; - - #[allow(clippy::as_conversions)] - let engine = call_with_last_error(|| match NonNull::new(engine as *mut c_void) { - Some(c) if res == TRUE => Some(c), - _ => None, - })?; - Ok(Self { inner: engine }) + Self::new(roots, 0) } #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result { - let mut root_store = CertificateStore::new()?; - root_store.add_cert(&root)?; - - let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); // We use these flags for the following reasons: // // - CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL is used in an attempt to stop Windows from using the internet to @@ -340,11 +314,29 @@ impl CertEngine { // data inside of a test and avoid any extra parsing, etc, it might need to do pulling directly from the store each time. // // Ref: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_engine_config - config.dwFlags = CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE; - config.hExclusiveRoot = root_store.inner.as_ptr(); + Self::new( + [root], + CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE, + ) + } + + fn new( + roots: impl IntoIterator>, + flags: u32, + ) -> Result { + let mut store = CertificateStore::new()?; + for root in roots { + store.add_cert(&root)?; + } + let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); + config.hExclusiveRoot = store.inner.as_ptr(); + config.dwFlags = flags; let mut engine = EnginePtr::NULL; - // Same workaround with as above when creating the engine. + + // XXX: Due to the redefinition of `CERT_CHAIN_ENGINE_CONFIG`, we need to do pointer casts + // in order to pass our expanded structure into `CertCreateCertificateChainEngine`. + // See also `CERT_CHAIN_PARA` casting below. let config = NonNull::from(&config).cast().as_ptr(); // SAFETY: `engine` is valid to be written to and the config is valid to be read. let res = unsafe { CertCreateCertificateChainEngine(config, &mut engine) }; From e491d73485e732b02fe8ecfc6eda5a5e87398cc0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:32:30 +0200 Subject: [PATCH 6/9] windows: clean up bogus Android guard --- rustls-platform-verifier/src/verification/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index c12374b..09429dc 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -88,7 +88,7 @@ impl Verifier { /// Creates a new instance of a TLS certificate verifier that utilizes the /// Windows certificate facilities and augmented by the provided extra root certificates. - #[cfg_attr(docsrs, doc(cfg(not(target_os = "android"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] pub fn new_with_extra_roots( roots: impl IntoIterator>, crypto_provider: Arc, From f92dbbb7de6c2cebcfbeff354160cfda0e596240 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:50:26 +0200 Subject: [PATCH 7/9] windows: stop creating throw-away store --- rustls-platform-verifier/src/verification/windows.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index 09429dc..82e6f67 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -385,9 +385,7 @@ impl CertificateStore { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result { let mut inner = Self::new()?; - - let mut root_store = Self::new()?; - root_store.add_cert(&root)?; + inner.add_cert(&root)?; let engine = CertEngine::new_with_fake_root(root)?; inner.engine = Some(engine); From 36c7a1040600258388918510011fc6b1f938b871 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 14:55:49 +0200 Subject: [PATCH 8/9] windows: rename new_with_fake_root() to for_testing() --- rustls-platform-verifier/src/verification/windows.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index 82e6f67..4ff9b0f 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -129,7 +129,7 @@ impl Verifier { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] let mut store = match self.test_only_root_ca_override.as_ref() { Some(test_only_root_ca_override) => { - CertificateStore::new_with_fake_root(test_only_root_ca_override.clone())? + CertificateStore::for_testing(test_only_root_ca_override.clone())? } None => CertificateStore::new()?, }; @@ -304,7 +304,7 @@ impl CertEngine { } #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result { + fn for_testing(root: pki_types::CertificateDer<'static>) -> Result { // We use these flags for the following reasons: // // - CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL is used in an attempt to stop Windows from using the internet to @@ -383,11 +383,11 @@ struct CertificateStore { impl CertificateStore { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] - fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result { + fn for_testing(root: pki_types::CertificateDer<'static>) -> Result { let mut inner = Self::new()?; inner.add_cert(&root)?; - let engine = CertEngine::new_with_fake_root(root)?; + let engine = CertEngine::for_testing(root)?; inner.engine = Some(engine); Ok(inner) From f4715a7f01c2f379d1ab5df523375cc3bba9c7f0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 8 Jul 2026 15:00:45 +0200 Subject: [PATCH 9/9] windows: add Verifier::offline() constructor --- .../src/verification/windows.rs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index 4ff9b0f..80ad9af 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -34,9 +34,7 @@ use rustls::{ SignatureScheme, }; #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] -use windows_sys::Win32::Security::Cryptography::{ - CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL, CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE, -}; +use windows_sys::Win32::Security::Cryptography::CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE; use windows_sys::Win32::{ Foundation::{ CERT_E_CN_NO_MATCH, CERT_E_EXPIRED, CERT_E_INVALID_NAME, CERT_E_UNTRUSTEDROOT, @@ -47,7 +45,7 @@ use windows_sys::Win32::{ CertFreeCertificateChain, CertFreeCertificateChainEngine, CertFreeCertificateContext, CertGetCertificateChain, CertOpenStore, CertSetCertificateContextProperty, CertVerifyCertificateChainPolicy, HTTPSPolicyCallbackData, AUTHTYPE_SERVER, - CERT_CHAIN_CACHE_END_CERT, CERT_CHAIN_CONTEXT, + CERT_CHAIN_CACHE_END_CERT, CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL, CERT_CHAIN_CONTEXT, CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS, CERT_CHAIN_POLICY_PARA, CERT_CHAIN_POLICY_SSL, CERT_CHAIN_POLICY_STATUS, CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT, CERT_CHAIN_REVOCATION_CHECK_END_CERT, @@ -102,6 +100,20 @@ impl Verifier { }) } + /// Creates a Windows TLS certificate verifier that avoids using online revocation checking. + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn offline( + roots: impl IntoIterator>, + crypto_provider: Arc, + ) -> Result { + Ok(Self { + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + test_only_root_ca_override: None, + crypto_provider, + extra_roots: Some(CertEngine::new(roots, CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL)?), + }) + } + /// Creates a test-only TLS certificate verifier which trusts our fake root CA cert. #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] pub(crate) fn new_with_fake_root(