diff --git a/Cargo.toml b/Cargo.toml index 69437f0c2..189d4c40e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,8 @@ rustls-tls-manual-roots = ["rustls-tls-manual-roots-no-provider", "__rustls-ring rustls-tls-webpki-roots = ["rustls-tls-webpki-roots-no-provider", "__rustls-ring"] rustls-tls-native-roots = ["rustls-tls-native-roots-no-provider", "__rustls-ring"] +rustls-platform-verifier-fallback = ["__rustls", "dep:rustls-platform-verifier"] + blocking = ["dep:futures-channel", "futures-channel?/sink", "dep:futures-util", "futures-util?/io", "futures-util?/sink", "tokio/sync"] charset = ["dep:encoding_rs", "dep:mime"] @@ -153,6 +155,7 @@ rustls = { version = "0.23.4", optional = true, default-features = false, featur tokio-rustls = { version = "0.26", optional = true, default-features = false, features = ["tls12"] } webpki-roots = { version = "1", optional = true } rustls-native-certs = { version = "0.8.0", optional = true } +rustls-platform-verifier = { version = "0.6", optional = true } ## cookies cookie_crate = { version = "0.18.0", package = "cookie", optional = true } diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 8f3bebb1e..f40dac9bd 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -637,6 +637,8 @@ impl ClientBuilder { } #[cfg(feature = "__rustls")] TlsBackend::Rustls => { + #[cfg(feature = "rustls-platform-verifier-fallback")] + use crate::tls::FallbackPlatformVerifier; use crate::tls::{IgnoreHostname, NoVerifier}; // Set root certificates. @@ -738,15 +740,67 @@ impl ClientBuilder { .dangerous() .with_custom_certificate_verifier(Arc::new(NoVerifier)) } else if !config.hostname_verification { - config_builder - .dangerous() - .with_custom_certificate_verifier(Arc::new(IgnoreHostname::new( - root_cert_store, - signature_algorithms, - ))) + let ignore_hostname_verifier = + Arc::new(IgnoreHostname::new(root_cert_store, signature_algorithms)); + + #[cfg(feature = "rustls-platform-verifier-fallback")] + { + let fallback_verifier = + FallbackPlatformVerifier::with_platform_fallback( + ignore_hostname_verifier, + provider, + ) + .map_err(|e| { + crate::error::builder(format!( + "failed to create platform verifier: {e:?}" + )) + })?; + config_builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(fallback_verifier)) + } + #[cfg(not(feature = "rustls-platform-verifier-fallback"))] + { + config_builder + .dangerous() + .with_custom_certificate_verifier(ignore_hostname_verifier) + } } else { if config.crls.is_empty() { - config_builder.with_root_certificates(root_cert_store) + #[cfg(feature = "rustls-platform-verifier-fallback")] + { + let standard_verifier = + rustls::client::WebPkiServerVerifier::builder_with_provider( + Arc::new(root_cert_store), + provider.clone(), + ) + .allow_unknown_revocation_status() + .build() + .map_err(|e| { + crate::error::builder(format!( + "invalid TLS verification settings: {e:?}" + )) + })?; + + let fallback_verifier = + FallbackPlatformVerifier::with_platform_fallback( + standard_verifier, + provider, + ) + .map_err(|e| { + crate::error::builder(format!( + "failed to create platform verifier: {e:?}" + )) + })?; + + config_builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(fallback_verifier)) + } + #[cfg(not(feature = "rustls-platform-verifier-fallback"))] + { + config_builder.with_root_certificates(root_cert_store) + } } else { let crls = config .crls @@ -756,14 +810,34 @@ impl ClientBuilder { let verifier = rustls::client::WebPkiServerVerifier::builder_with_provider( Arc::new(root_cert_store), - provider, + provider.clone(), ) .with_crls(crls) .build() .map_err(|_| { crate::error::builder("invalid TLS verification settings") })?; - config_builder.with_webpki_verifier(verifier) + + #[cfg(feature = "rustls-platform-verifier-fallback")] + { + let fallback_verifier = + FallbackPlatformVerifier::with_platform_fallback( + verifier, provider, + ) + .map_err(|e| { + crate::error::builder(format!( + "failed to create platform verifier: {e:?}" + )) + })?; + + config_builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(fallback_verifier)) + } + #[cfg(not(feature = "rustls-platform-verifier-fallback"))] + { + config_builder.with_webpki_verifier(verifier) + } } }; diff --git a/src/tls.rs b/src/tls.rs index e14d31008..301934218 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -54,6 +54,8 @@ use rustls::{ use rustls_pki_types::pem::PemObject; #[cfg(feature = "__rustls")] use rustls_pki_types::{ServerName, UnixTime}; +#[cfg(feature = "rustls-platform-verifier-fallback")] +use std::sync::Arc; use std::{ fmt, io::{BufRead, BufReader}, @@ -722,6 +724,86 @@ impl ServerCertVerifier for IgnoreHostname { } } +/// A certificate verifier that tries a primary verifier first, +/// and falls back to a platform verifier if the primary fails. +#[cfg(feature = "rustls-platform-verifier-fallback")] +#[derive(Debug)] +pub(crate) struct FallbackPlatformVerifier { + primary: Arc, + fallback: Arc, +} + +#[cfg(feature = "rustls-platform-verifier-fallback")] +impl FallbackPlatformVerifier { + pub(crate) fn with_platform_fallback( + primary: Arc, + provider: Arc, + ) -> Result { + let fallback = Arc::new(rustls_platform_verifier::Verifier::new(provider)?); + Ok(Self { primary, fallback }) + } +} + +#[cfg(feature = "rustls-platform-verifier-fallback")] +impl ServerCertVerifier for FallbackPlatformVerifier { + fn verify_server_cert( + &self, + end_entity: &rustls_pki_types::CertificateDer<'_>, + intermediates: &[rustls_pki_types::CertificateDer<'_>], + server_name: &ServerName<'_>, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + match self.primary.verify_server_cert( + end_entity, + intermediates, + server_name, + ocsp_response, + now, + ) { + Ok(verified) => Ok(verified), + Err(primary_err) => { + match self.fallback.verify_server_cert( + end_entity, + intermediates, + server_name, + ocsp_response, + now, + ) { + Ok(verified) => Ok(verified), + Err(_) => Err(primary_err), + } + } + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &rustls_pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + // Both use rustls::crypto::verify_tls12_signature + self.primary.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &rustls_pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + // Both use rustls::crypto::verify_tls13_signature + self.primary.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec { + // Both verifiers use the same CryptoProvider, so they support the same + // signature schemes. Just return the primary's schemes. + self.primary.supported_verify_schemes() + } +} + /// Hyper extension carrying extra TLS layer information. /// Made available to clients on responses when `tls_info` is set. #[derive(Clone)]