diff --git a/CHANGELOG.md b/CHANGELOG.md index 237c1d6a35..d2a2acc4b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +[#4045]: https://github.com/launchbadge/sqlx/pull/4045 + ## 0.9.0 - 2026-05-06 ### Important Announcements diff --git a/Cargo.toml b/Cargo.toml index 3738b814ca..8ce2abc007 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,9 +103,11 @@ runtime-tokio = ["_rt-tokio", "sqlx-core/_rt-tokio", "sqlx-macros?/_rt-tokio"] tls-native-tls = ["sqlx-core/_tls-native-tls", "sqlx-macros?/_tls-native-tls"] tls-rustls = ["tls-rustls-ring"] # For backwards compatibility tls-rustls-aws-lc-rs = ["sqlx-core/_tls-rustls-aws-lc-rs", "sqlx-macros?/_tls-rustls-aws-lc-rs"] +tls-rustls-aws-lc-rs-platform-verifier = ["sqlx-core/_tls-rustls-aws-lc-rs-platform-verifier", "sqlx-macros?/_tls-rustls-aws-lc-rs-platform-verifier"] tls-rustls-ring = ["tls-rustls-ring-webpki"] # For backwards compatibility tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki", "sqlx-macros?/_tls-rustls-ring-webpki"] -tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots", "sqlx-macros?/_tls-rustls-ring-native-roots"] +tls-rustls-ring-native-roots = ["tls-rustls-ring-platform-verifier"] # For backwards compatibility +tls-rustls-ring-platform-verifier = ["sqlx-core/_tls-rustls-aws-lc-rs-platform-verifier", "sqlx-macros?/_tls-rustls-aws-lc-rs-platform-verifier"] # No-op feature used by the workflows to compile without TLS enabled. Not meant for general use. tls-none = [] diff --git a/README.md b/README.md index ff98f45350..0cfcc8c5ac 100644 --- a/README.md +++ b/README.md @@ -139,9 +139,11 @@ sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls" ] } # tokio + rustls with ring and WebPKI CA certificates sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-webpki" ] } # tokio + rustls with ring and platform's native CA certificates -sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-native-roots" ] } +sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-platform-verifier" ] } # tokio + rustls with aws-lc-rs sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs" ] } +# tokio + rustls with aws-lc-rs and platform's native CA certificates +sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs-platform-verifier" ] } # async-std (no TLS) sqlx = { version = "0.8", features = [ "runtime-async-std" ] } @@ -150,9 +152,11 @@ sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-native-tls" ] } # async-std + rustls with ring and WebPKI CA certificates sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-webpki" ] } # async-std + rustls with ring and platform's native CA certificates -sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-native-roots" ] } +sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-platform-verifier" ] } # async-std + rustls with aws-lc-rs sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-aws-lc-rs" ] } +# async-std + rustls with aws-lc-rs and platform's native CA certificates +sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-aws-lc-rs-platform-verifier" ] } ``` #### Cargo Feature Flags diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index f283ba747b..fbc6410b4d 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -58,6 +58,9 @@ default = ["postgres", "sqlite", "mysql", "native-tls", "completions", "sqlx-tom # TLS options rustls = ["sqlx/tls-rustls"] native-tls = ["sqlx/tls-native-tls"] +tls-rustls-aws-lc-rs-platform-verifier = ["sqlx/tls-rustls-aws-lc-rs-platform-verifier"] +tls-rustls-ring-platform-verifier = ["sqlx/tls-rustls-aws-lc-rs-platform-verifier"] + # databases mysql = ["sqlx/mysql"] diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 5c20bad050..6ffc06eed5 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -29,8 +29,9 @@ _rt-tokio = ["tokio", "tokio-stream"] _tls-native-tls = ["native-tls"] _tls-rustls-aws-lc-rs = ["_tls-rustls", "rustls/aws-lc-rs", "webpki-roots"] +_tls-rustls-aws-lc-rs-platform-verifier = ["_tls-rustls", "rustls/aws-lc-rs", "rustls-platform-verifier"] _tls-rustls-ring-webpki = ["_tls-rustls", "rustls/ring", "webpki-roots"] -_tls-rustls-ring-native-roots = ["_tls-rustls", "rustls/ring", "rustls-native-certs"] +_tls-rustls-ring-platform-verifier = ["_tls-rustls", "rustls/ring", "rustls-platform-verifier"] _tls-rustls = ["rustls"] _tls-none = [] @@ -57,7 +58,7 @@ native-tls = { version = "0.2.10", optional = true } rustls = { version = "0.23.24", default-features = false, features = ["std", "tls12"], optional = true } webpki-roots = { version = "1", optional = true } -rustls-native-certs = { version = "0.8.0", optional = true } +rustls-platform-verifier = { version = "0.7", optional = true } # Type Integrations bit-vec = { workspace = true, optional = true } diff --git a/sqlx-core/src/net/tls/tls_rustls.rs b/sqlx-core/src/net/tls/tls_rustls.rs index 1ecbbad519..0c61a3e2be 100644 --- a/sqlx-core/src/net/tls/tls_rustls.rs +++ b/sqlx-core/src/net/tls/tls_rustls.rs @@ -4,16 +4,13 @@ use std::sync::Arc; use std::task::{ready, Context, Poll}; use rustls::{ - client::{ - danger::{ServerCertVerified, ServerCertVerifier}, - WebPkiServerVerifier, - }, + client::danger::{ServerCertVerified, ServerCertVerifier}, crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, pki_types::{ pem::{self, PemObject}, CertificateDer, PrivateKeyDer, ServerName, UnixTime, }, - CertificateError, ClientConfig, ClientConnection, Error as TlsError, RootCertStore, + CertificateError, ClientConfig, ClientConnection, Error as TlsError, }; use crate::error::Error; @@ -92,14 +89,17 @@ where S: Socket, { #[cfg(all( - feature = "_tls-rustls-aws-lc-rs", + any( + feature = "_tls-rustls-aws-lc-rs", + feature = "_tls-rustls-aws-lc-rs-platform-verifier" + ), not(feature = "_tls-rustls-ring-webpki"), - not(feature = "_tls-rustls-ring-native-roots") + not(feature = "_tls-rustls-ring-platform-verifier"), ))] let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); #[cfg(any( feature = "_tls-rustls-ring-webpki", - feature = "_tls-rustls-ring-native-roots" + feature = "_tls-rustls-ring-platform-verifier" ))] let provider = Arc::new(rustls::crypto::ring::default_provider()); @@ -136,46 +136,53 @@ where .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) .with_no_client_auth() } - } else { - let mut cert_store = import_root_certs(); - - if let Some(ca) = tls_config.root_cert_path { - let data = ca.data().await?; - - for result in CertificateDer::pem_slice_iter(&data) { - let Ok(cert) = result else { - return Err(Error::Tls(format!("Invalid certificate {ca}").into())); - }; + } else if tls_config.accept_invalid_hostnames { + #[cfg(feature = "rustls-platform-verifier")] + let verifier = rustls_platform_verifier::Verifier::new(config.crypto_provider().clone()) + .map(Arc::new) + .map_err(Error::tls)?; + + #[cfg(not(feature = "rustls-platform-verifier"))] + let verifier = rustls::client::WebPkiServerVerifier::builder(Arc::new( + load_root_certs(&tls_config).await?, + )) + .build() + .map_err(Error::tls)?; - cert_store.add(cert).map_err(|err| Error::Tls(err.into()))?; - } + if let Some(user_auth) = user_auth { + config + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) + .with_client_auth_cert(user_auth.0, user_auth.1) + .map_err(Error::tls)? + } else { + config + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) + .with_no_client_auth() + } + } else { + #[cfg(feature = "rustls-platform-verifier")] + if let Some(user_auth) = user_auth { + rustls_platform_verifier::BuilderVerifierExt::with_platform_verifier(config) + .map_err(Error::tls)? + .with_client_auth_cert(user_auth.0, user_auth.1) + .map_err(Error::tls)? + } else { + rustls_platform_verifier::BuilderVerifierExt::with_platform_verifier(config) + .map_err(Error::tls)? + .with_no_client_auth() } - if tls_config.accept_invalid_hostnames { - let verifier = WebPkiServerVerifier::builder(Arc::new(cert_store)) - .build() - .map_err(|err| Error::Tls(err.into()))?; - - if let Some(user_auth) = user_auth { - config - .dangerous() - .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) - .with_client_auth_cert(user_auth.0, user_auth.1) - .map_err(Error::tls)? - } else { - config - .dangerous() - .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) - .with_no_client_auth() - } - } else if let Some(user_auth) = user_auth { + #[cfg(not(feature = "rustls-platform-verifier"))] + if let Some(user_auth) = user_auth { config - .with_root_certificates(cert_store) + .with_root_certificates(load_root_certs(&tls_config).await?) .with_client_auth_cert(user_auth.0, user_auth.1) .map_err(Error::tls)? } else { config - .with_root_certificates(cert_store) + .with_root_certificates(load_root_certs(&tls_config).await?) .with_no_client_auth() } }; @@ -196,7 +203,7 @@ where fn certs_from_pem(pem: Vec) -> Result>, Error> { CertificateDer::pem_slice_iter(&pem) - .map(|result| result.map_err(|err| Error::Tls(err.into()))) + .map(|result| result.map_err(Error::tls)) .collect() } @@ -208,32 +215,28 @@ fn private_key_from_pem(pem: Vec) -> Result, Error> { } } -#[cfg(all(feature = "webpki-roots", not(feature = "rustls-native-certs")))] -fn import_root_certs() -> RootCertStore { - RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()) -} +#[cfg(all(feature = "webpki-roots", not(feature = "rustls-platform-verifier")))] +async fn load_root_certs(tls_config: &TlsConfig<'_>) -> Result { + let mut cert_store = + rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + if let Some(ca) = tls_config.root_cert_path { + let data = ca.data().await?; -#[cfg(feature = "rustls-native-certs")] -fn import_root_certs() -> RootCertStore { - let mut root_cert_store = RootCertStore::empty(); + for result in CertificateDer::pem_slice_iter(&data) { + let Ok(cert) = result else { + return Err(Error::tls(format!("Invalid certificate {ca}"))); + }; - let load_results = rustls_native_certs::load_native_certs(); - for e in load_results.errors { - log::warn!("Error loading native certificates: {e:?}"); - } - for cert in load_results.certs { - if let Err(e) = root_cert_store.add(cert) { - log::warn!("rustls failed to parse native certificate: {e:?}"); + cert_store.add(cert).map_err(Error::tls)?; } } - - root_cert_store + Ok(cert_store) } // Not currently used but allows for a "tls-rustls-no-roots" feature. -#[cfg(not(any(feature = "rustls-native-certs", feature = "webpki-roots")))] -fn import_root_certs() -> RootCertStore { - RootCertStore::empty() +#[cfg(not(any(feature = "rustls-platform-verifier", feature = "webpki-roots")))] +async fn load_root_certs() -> Result { + Ok(rustls::RootCertStore::empty()) } #[derive(Debug)] @@ -289,11 +292,14 @@ impl ServerCertVerifier for DummyTlsVerifier { } #[derive(Debug)] -pub struct NoHostnameTlsVerifier { - verifier: Arc, +pub struct NoHostnameTlsVerifier { + verifier: Arc, } -impl ServerCertVerifier for NoHostnameTlsVerifier { +impl ServerCertVerifier for NoHostnameTlsVerifier +where + T: ServerCertVerifier, +{ fn verify_server_cert( &self, end_entity: &CertificateDer<'_>, diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml index 534b92764d..72576d6817 100644 --- a/sqlx-macros-core/Cargo.toml +++ b/sqlx-macros-core/Cargo.toml @@ -19,8 +19,9 @@ _rt-tokio = ["tokio", "sqlx-core/_rt-tokio"] _tls-native-tls = ["sqlx-core/_tls-native-tls"] _tls-rustls-aws-lc-rs = ["sqlx-core/_tls-rustls-aws-lc-rs"] +_tls-rustls-aws-lc-rs-platform-verifier = ["sqlx-core/_tls-rustls-aws-lc-rs-platform-verifier"] _tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki"] -_tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots"] +_tls-rustls-ring-platform-verifier = ["sqlx-core/_tls-rustls-aws-lc-rs-platform-verifier"] _sqlite = [] diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index bd90da9608..30e0800770 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -22,8 +22,9 @@ _rt-tokio = ["sqlx-macros-core/_rt-tokio"] _tls-native-tls = ["sqlx-macros-core/_tls-native-tls"] _tls-rustls-aws-lc-rs = ["sqlx-macros-core/_tls-rustls-aws-lc-rs"] +_tls-rustls-aws-lc-rs-platform-verifier = ["sqlx-macros-core/_tls-rustls-aws-lc-rs-platform-verifier"] _tls-rustls-ring-webpki = ["sqlx-macros-core/_tls-rustls-ring-webpki"] -_tls-rustls-ring-native-roots = ["sqlx-macros-core/_tls-rustls-ring-native-roots"] +_tls-rustls-ring-platform-verifier = ["sqlx-macros-core/_tls-rustls-aws-lc-rs-platform-verifier"] # SQLx features derive = ["sqlx-macros-core/derive"] diff --git a/sqlx-postgres/src/options/doc.md b/sqlx-postgres/src/options/doc.md index 33dd63b7a8..b67b0d1eb1 100644 --- a/sqlx-postgres/src/options/doc.md +++ b/sqlx-postgres/src/options/doc.md @@ -86,7 +86,8 @@ See `default_host()` in the same source file as this method for the current beha If `sslrootcert` is not set, the default root certificates used depends on Cargo features: * If `tls-native-tls` is enabled, the system root certificates are used. -* If `tls-rustls-ring-native-roots` is enabled, the system root certificates are used. +* If `tls-rustls-ring-platform-verifier` or `tls-rustls-aws-lc-rs-platform-verifier` + is enabled, the system root certificates are used. * Otherwise, TLS roots are populated using the [`webpki-roots`] crate. ## Environment Variables