Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" ] }
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions sqlx-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
5 changes: 3 additions & 2 deletions sqlx-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand All @@ -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 }
Expand Down
132 changes: 69 additions & 63 deletions sqlx-core/src/net/tls/tls_rustls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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()
}
};
Expand All @@ -196,7 +203,7 @@ where

fn certs_from_pem(pem: Vec<u8>) -> Result<Vec<CertificateDer<'static>>, Error> {
CertificateDer::pem_slice_iter(&pem)
.map(|result| result.map_err(|err| Error::Tls(err.into())))
.map(|result| result.map_err(Error::tls))
.collect()
}

Expand All @@ -208,32 +215,28 @@ fn private_key_from_pem(pem: Vec<u8>) -> Result<PrivateKeyDer<'static>, 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<rustls::RootCertStore, Error> {
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<rustls::RootCertStore, Error> {
Ok(rustls::RootCertStore::empty())
}

#[derive(Debug)]
Expand Down Expand Up @@ -289,11 +292,14 @@ impl ServerCertVerifier for DummyTlsVerifier {
}

#[derive(Debug)]
pub struct NoHostnameTlsVerifier {
verifier: Arc<WebPkiServerVerifier>,
pub struct NoHostnameTlsVerifier<T> {
verifier: Arc<T>,
}

impl ServerCertVerifier for NoHostnameTlsVerifier {
impl<T> ServerCertVerifier for NoHostnameTlsVerifier<T>
where
T: ServerCertVerifier,
{
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
Expand Down
3 changes: 2 additions & 1 deletion sqlx-macros-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down
3 changes: 2 additions & 1 deletion sqlx-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
3 changes: 2 additions & 1 deletion sqlx-postgres/src/options/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading