From 54df594d6cf4570f78525101e7e6e3a7cbe4b6a4 Mon Sep 17 00:00:00 2001 From: Jonas Osburg Date: Wed, 21 Aug 2024 10:29:10 +0200 Subject: [PATCH] Implement accept_invalid_hostnames for rustls (#977) Fixes #957 Co-authored-by: Paolo Barbolini --- Cargo.lock | 5 +- Cargo.toml | 3 +- src/transport/smtp/client/tls.rs | 126 ++++++++++++++++++++----------- 3 files changed, 86 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fa3c66..09122e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1322,6 +1322,7 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "sha2", @@ -2026,9 +2027,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" diff --git a/Cargo.toml b/Cargo.toml index 5f8f73f..a05abed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ native-tls = { version = "0.2.5", optional = true } # feature rustls = { version = "0.23.5", default-features = false, features = ["ring", "logging", "std", "tls12"], optional = true } rustls-pemfile = { version = "2", optional = true } rustls-native-certs = { version = "0.7", optional = true } +rustls-pki-types = { version = "1.7", optional = true } webpki-roots = { version = "0.26", optional = true } boring = { version = "4", optional = true } @@ -108,7 +109,7 @@ smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:url", "dep:percen pool = ["dep:futures-util"] -rustls-tls = ["dep:webpki-roots", "dep:rustls", "dep:rustls-pemfile"] +rustls-tls = ["dep:webpki-roots", "dep:rustls", "dep:rustls-pemfile", "dep:rustls-pki-types"] boring-tls = ["dep:boring"] diff --git a/src/transport/smtp/client/tls.rs b/src/transport/smtp/client/tls.rs index 89d397c..92dca93 100644 --- a/src/transport/smtp/client/tls.rs +++ b/src/transport/smtp/client/tls.rs @@ -13,8 +13,10 @@ use native_tls::{Protocol, TlsConnector}; #[cfg(feature = "rustls-tls")] use rustls::{ client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::WebPkiSupportedAlgorithms, crypto::{verify_tls12_signature, verify_tls13_signature}, pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime}, + server::ParsedCertificate, ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme, }; @@ -195,10 +197,11 @@ impl TlsParametersBuilder { /// including those from other sites, are trusted. /// /// This method introduces significant vulnerabilities to man-in-the-middle attacks. - /// - /// Hostname verification can only be disabled with the `native-tls` TLS backend. #[cfg(any(feature = "native-tls", feature = "boring-tls"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "boring-tls"))))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))) + )] pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self { self.accept_invalid_hostnames = accept_invalid_hostnames; self @@ -376,50 +379,63 @@ impl TlsParametersBuilder { }; let tls = ClientConfig::builder_with_protocol_versions(supported_versions); + let provider = rustls::crypto::CryptoProvider::get_default() + .map(|arc| arc.clone()) + .unwrap_or_else(|| Arc::new(rustls::crypto::ring::default_provider())); - let tls = if self.accept_invalid_certs { - tls.dangerous() - .with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {})) - } else { - let mut root_cert_store = RootCertStore::empty(); + // Build TLS config + let signature_algorithms = provider.signature_verification_algorithms; - #[cfg(feature = "rustls-native-certs")] - fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> { - let native_certs = rustls_native_certs::load_native_certs().map_err(error::tls)?; - let (added, ignored) = store.add_parsable_certificates(native_certs); - #[cfg(feature = "tracing")] - tracing::debug!( - "loaded platform certs with {added} valid and {ignored} ignored (invalid) certs" - ); - Ok(()) + let mut root_cert_store = RootCertStore::empty(); + + #[cfg(feature = "rustls-native-certs")] + fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> { + let native_certs = rustls_native_certs::load_native_certs().map_err(error::tls)?; + let (added, ignored) = store.add_parsable_certificates(native_certs); + #[cfg(feature = "tracing")] + tracing::debug!( + "loaded platform certs with {added} valid and {ignored} ignored (invalid) certs" + ); + Ok(()) + } + + #[cfg(feature = "rustls-tls")] + fn load_webpki_roots(store: &mut RootCertStore) { + store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + } + + match self.cert_store { + CertificateStore::Default => { + #[cfg(feature = "rustls-native-certs")] + load_native_roots(&mut root_cert_store)?; + #[cfg(not(feature = "rustls-native-certs"))] + load_webpki_roots(&mut root_cert_store); } - #[cfg(feature = "rustls-tls")] - fn load_webpki_roots(store: &mut RootCertStore) { - store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - } - - match self.cert_store { - CertificateStore::Default => { - #[cfg(feature = "rustls-native-certs")] - load_native_roots(&mut root_cert_store)?; - #[cfg(not(feature = "rustls-native-certs"))] - load_webpki_roots(&mut root_cert_store); - } - #[cfg(feature = "rustls-tls")] - CertificateStore::WebpkiRoots => { - load_webpki_roots(&mut root_cert_store); - } - CertificateStore::None => {} - } - for cert in self.root_certs { - for rustls_cert in cert.rustls { - root_cert_store.add(rustls_cert).map_err(error::tls)?; - } + CertificateStore::WebpkiRoots => { + load_webpki_roots(&mut root_cert_store); } + CertificateStore::None => {} + } + for cert in self.root_certs { + for rustls_cert in cert.rustls { + root_cert_store.add(rustls_cert).map_err(error::tls)?; + } + } + let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames { + let verifier = InvalidCertsVerifier { + ignore_invalid_hostnames: self.accept_invalid_hostnames, + ignore_invalid_certs: self.accept_invalid_certs, + roots: root_cert_store, + signature_algorithms, + }; + tls.dangerous() + .with_custom_certificate_verifier(Arc::new(verifier)) + } else { tls.with_root_certificates(root_cert_store) }; + let tls = if let Some(identity) = self.identity { let (client_certificates, private_key) = identity.rustls_tls; tls.with_client_auth_cert(client_certificates, private_key) @@ -629,18 +645,38 @@ impl Identity { #[cfg(feature = "rustls-tls")] #[derive(Debug)] -struct InvalidCertsVerifier; +struct InvalidCertsVerifier { + ignore_invalid_hostnames: bool, + ignore_invalid_certs: bool, + roots: RootCertStore, + signature_algorithms: WebPkiSupportedAlgorithms, +} #[cfg(feature = "rustls-tls")] impl ServerCertVerifier for InvalidCertsVerifier { fn verify_server_cert( &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &ServerName<'_>, + ocsp_response: &[u8], + now: UnixTime, ) -> Result { + let cert = ParsedCertificate::try_from(end_entity)?; + + if !self.ignore_invalid_certs { + rustls::client::verify_server_cert_signed_by_trust_anchor( + &cert, + &self.roots, + intermediates, + now, + self.signature_algorithms.all, + )?; + } + + if !self.ignore_invalid_hostnames { + rustls::client::verify_server_name(&cert, server_name)?; + } Ok(ServerCertVerified::assertion()) }