From d3d8e248245bf08834c6ae7cef497be2d8b3b067 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Mon, 2 Jun 2025 10:43:17 +0200 Subject: [PATCH] feat: add `rustls-platform-verifier` support (#1081) --- Cargo.lock | 197 ++++++++++++++++++++++++++++--- Cargo.toml | 1 + src/lib.rs | 25 ++-- src/transport/smtp/client/tls.rs | 57 +++++++-- 4 files changed, 245 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc60567..4c73145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,7 +291,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -437,6 +437,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -534,6 +540,16 @@ dependencies = [ "cc", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1241,6 +1257,28 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.33" @@ -1316,6 +1354,7 @@ dependencies = [ "rsa", "rustls", "rustls-native-certs", + "rustls-platform-verifier", "serde", "serde_json", "sha2", @@ -1346,7 +1385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1972,9 +2011,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", @@ -2005,10 +2044,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] -name = "rustls-webpki" -version = "0.103.1" +name = "rustls-platform-verifier" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "eda84358ed17f1f354cf4b1909ad346e6c7bc2513e8c40eb08e0157aa13a9070" +dependencies = [ + "core-foundation 0.10.0", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.2.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring", @@ -2277,6 +2343,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -2602,6 +2688,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.0" @@ -2661,7 +2756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2670,7 +2765,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -2679,7 +2783,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2688,7 +2792,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2697,28 +2816,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2731,24 +2868,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index a9f83a8..815ab50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ percent-encoding = { version = "2.3", optional = true } ## tls native-tls = { version = "0.2.9", optional = true } # feature rustls = { version = "0.23.18", default-features = false, features = ["logging", "std", "tls12"], optional = true } +rustls-platform-verifier = { version = "0.6.0", optional = true } rustls-native-certs = { version = "0.8", optional = true } webpki-roots = { version = "1.0.0", optional = true } boring = { version = "4", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 7a8f685..f0a1bf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,17 +93,20 @@ //! When the `rustls` feature is enabled, one of the following verification backends //! MUST also be enabled. //! -//! * **rustls-native-certs**: verify TLS certificates using the platform's native certificate store (see [`rustls-native-certs`]) +//! * **rustls-platform-verifier**: verify TLS certificate using the OS's native certificate store (see [`rustls-platform-verifier`]) +//! * **rustls-native-certs**: verify TLS certificates using the platform's native certificate store (see [`rustls-native-certs`]) - when in doubt use `rustls-platform-verifier` //! * **webpki-roots**: verify TLS certificates against Mozilla's root certificates (see [`webpki-roots`]) //! -//! For the `rustls-native-certs` backend to work correctly, the following packages -//! will need to be installed in order for the build stage and the compiled program -//! to run properly. +//! The following packages will need to be installed in order for the build +//! stage and the compiled program to run properly. //! -//! | Distro | Build-time packages | Runtime packages | -//! | ------------ | -------------------------- | ---------------------------- | -//! | Debian | none | `ca-certificates` | -//! | Alpine Linux | none | `ca-certificates` | +//! | Verification backend | Distro | Build-time packages | Runtime packages | +//! | --------------------- | ------------ | -------------------------- | ---------------------------- | +//! | `rustls-platform-verifier` | Debian | none | `ca-certificates` | +//! | `rustls-platform-verifier` | Alpine Linux | none | `ca-certificates` | +//! | `rustls-native-certs` | Debian | none | `ca-certificates` | +//! | `rustls-native-certs` | Alpine Linux | none | `ca-certificates` | +//! | `webpki-roots` | any | none | none | //! //! ### Sendmail transport //! @@ -151,6 +154,7 @@ //! [AWS-LC]: https://github.com/aws/aws-lc //! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs //! [`ring`]: https://crates.io/crates/ring +//! [`rustls-platform-verifier`]: https://crates.io/crates/rustls-platform-verifier //! [`rustls-native-certs`]: https://crates.io/crates/rustls-native-certs //! [`webpki-roots`]: https://crates.io/crates/webpki-roots //! [Tokio 1.x]: https://docs.rs/tokio/1 @@ -208,12 +212,13 @@ mod compiletime_checks { #[cfg(all( feature = "rustls", + not(feature = "rustls-platform-verifier"), not(feature = "rustls-native-certs"), not(feature = "webpki-roots") ))] compile_error!( - "feature `rustls` also requires either the `rustls-native-certs` or the `webpki-roots` feature to - be enabled" + "feature `rustls` also requires either the `rustls-platform-verifier`, the `rustls-native-certs` + or the `webpki-roots` feature to be enabled" ); #[cfg(all(feature = "native-tls", feature = "boring-tls"))] diff --git a/src/transport/smtp/client/tls.rs b/src/transport/smtp/client/tls.rs index a7fcb8e..f713861 100644 --- a/src/transport/smtp/client/tls.rs +++ b/src/transport/smtp/client/tls.rs @@ -164,8 +164,9 @@ pub enum CertificateStore { /// For native-tls, this will use the system certificate store on Windows, the keychain on /// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`). /// - /// For rustls, this will also use the system store if the `rustls-native-certs` feature is - /// enabled, or will fall back to `webpki-roots`. + /// For rustls, this will use the system certificate verifier if the `rustls-platform-verifier` + /// feature is enabled. If the `rustls-native-certs` feature is enabled, system certificate + /// store will be used. Otherwise, it will fall back to `webpki-roots`. /// /// The boring-tls backend uses the same logic as OpenSSL on all platforms. #[default] @@ -259,6 +260,8 @@ impl TlsParametersBuilder { /// Controls whether certificates with an invalid hostname are accepted /// + /// This option is silently disabled when using `rustls-platform-verifier`. + /// /// Defaults to `false`. /// /// # Warning @@ -461,7 +464,10 @@ impl TlsParametersBuilder { // Build TLS config let mut root_cert_store = RootCertStore::empty(); - #[cfg(feature = "rustls-native-certs")] + #[cfg(all( + not(feature = "rustls-platform-verifier"), + feature = "rustls-native-certs" + ))] fn load_native_roots(store: &mut RootCertStore) { let rustls_native_certs::CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs(); @@ -481,11 +487,26 @@ impl TlsParametersBuilder { store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); } + #[cfg_attr(not(feature = "rustls-platform-verifier"), allow(unused_mut))] + let mut extra_roots = None::>>; match self.cert_store { CertificateStore::Default => { - #[cfg(feature = "rustls-native-certs")] + #[cfg(feature = "rustls-platform-verifier")] + { + extra_roots = Some(Vec::new()); + } + + #[cfg(all( + not(feature = "rustls-platform-verifier"), + feature = "rustls-native-certs" + ))] load_native_roots(&mut root_cert_store); - #[cfg(all(not(feature = "rustls-native-certs"), feature = "webpki-roots"))] + + #[cfg(all( + not(feature = "rustls-platform-verifier"), + not(feature = "rustls-native-certs"), + feature = "webpki-roots" + ))] load_webpki_roots(&mut root_cert_store); } #[cfg(all(feature = "rustls", feature = "webpki-roots"))] @@ -496,11 +517,17 @@ impl TlsParametersBuilder { } for cert in self.root_certs { for rustls_cert in cert.rustls { + #[cfg(feature = "rustls-platform-verifier")] + if let Some(extra_roots) = &mut extra_roots { + extra_roots.push(rustls_cert.clone()); + } root_cert_store.add(rustls_cert).map_err(error::tls)?; } } - let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames { + let tls = if self.accept_invalid_certs + || (extra_roots.is_none() && self.accept_invalid_hostnames) + { let verifier = InvalidCertsVerifier { ignore_invalid_hostnames: self.accept_invalid_hostnames, ignore_invalid_certs: self.accept_invalid_certs, @@ -510,7 +537,23 @@ impl TlsParametersBuilder { tls.dangerous() .with_custom_certificate_verifier(Arc::new(verifier)) } else { - tls.with_root_certificates(root_cert_store) + #[cfg(feature = "rustls-platform-verifier")] + if let Some(extra_roots) = extra_roots { + tls.dangerous().with_custom_certificate_verifier(Arc::new( + rustls_platform_verifier::Verifier::new_with_extra_roots( + extra_roots, + crypto_provider, + ) + .map_err(error::tls)?, + )) + } else { + tls.with_root_certificates(root_cert_store) + } + + #[cfg(not(feature = "rustls-platform-verifier"))] + { + tls.with_root_certificates(root_cert_store) + } }; let tls = if let Some(identity) = self.identity {