Support building with rustls but native certificates (#3551)

This adds an alternative TLS configuration that relies on rustls-native-certs,
for users who cannot bundle the MPL-licensed webpki-roots.

The approach is copied from reqwest:

* https://github.com/seanmonstar/reqwest/blob/3ad6e02cd/Cargo.toml#L48
* https://github.com/seanmonstar/reqwest/blob/3ad6e02cd/src/async_impl/client.rs#L513

— except error handling is relaxed to accommodate for tls_config.root_cert_path.
This commit is contained in:
Ilya Bizyaev 2024-11-28 01:39:18 +01:00 committed by GitHub
parent 3e140ba384
commit 35c78f5175
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 69 additions and 14 deletions

14
Cargo.lock generated
View file

@ -3119,6 +3119,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.2"
@ -3540,6 +3553,7 @@ dependencies = [
"regex",
"rust_decimal",
"rustls",
"rustls-native-certs",
"rustls-pemfile",
"serde",
"serde_json",

View file

@ -82,7 +82,9 @@ 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-ring = ["sqlx-core/_tls-rustls-ring", "sqlx-macros?/_tls-rustls-ring"]
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"]
# No-op feature used by the workflows to compile without TLS enabled. Not meant for general use.
tls-none = []

View file

@ -136,8 +136,10 @@ SQLx is compatible with the [`async-std`], [`tokio`], and [`actix`] runtimes; an
sqlx = { version = "0.8", features = [ "runtime-tokio" ] }
# tokio + native-tls
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls" ] }
# tokio + rustls with ring
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring" ] }
# 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" ] }
# tokio + rustls with aws-lc-rs
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs" ] }
@ -145,8 +147,10 @@ sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs" ]
sqlx = { version = "0.8", features = [ "runtime-async-std" ] }
# async-std + native-tls
sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-native-tls" ] }
# async-std + rustls with ring
sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring" ] }
# 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" ] }
# async-std + rustls with aws-lc-rs
sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-aws-lc-rs" ] }
```

View file

@ -22,9 +22,10 @@ json = ["serde", "serde_json"]
_rt-async-std = ["async-std", "async-io"]
_rt-tokio = ["tokio", "tokio-stream"]
_tls-native-tls = ["native-tls"]
_tls-rustls-aws-lc-rs = ["_tls-rustls", "rustls/aws-lc-rs"]
_tls-rustls-ring = ["_tls-rustls", "rustls/ring"]
_tls-rustls = ["rustls", "rustls-pemfile", "webpki-roots"]
_tls-rustls-aws-lc-rs = ["_tls-rustls", "rustls/aws-lc-rs", "webpki-roots"]
_tls-rustls-ring-webpki = ["_tls-rustls", "rustls/ring", "webpki-roots"]
_tls-rustls-ring-native-roots = ["_tls-rustls", "rustls/ring", "rustls-native-certs"]
_tls-rustls = ["rustls", "rustls-pemfile"]
_tls-none = []
# support offline/decoupled building (enables serialization of `Describe`)
@ -41,6 +42,7 @@ native-tls = { version = "0.2.10", optional = true }
rustls = { version = "0.23.11", default-features = false, features = ["std", "tls12"], optional = true }
rustls-pemfile = { version = "2", optional = true }
webpki-roots = { version = "0.26", optional = true }
rustls-native-certs = { version = "0.8.0", optional = true }
# Type Integrations
bit-vec = { workspace = true, optional = true }

View file

@ -88,9 +88,16 @@ pub async fn handshake<S>(socket: S, tls_config: TlsConfig<'_>) -> Result<Rustls
where
S: Socket,
{
#[cfg(all(feature = "_tls-rustls-aws-lc-rs", not(feature = "_tls-rustls-ring")))]
#[cfg(all(
feature = "_tls-rustls-aws-lc-rs",
not(feature = "_tls-rustls-ring-webpki"),
not(feature = "_tls-rustls-ring-native-roots")
))]
let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
#[cfg(feature = "_tls-rustls-ring")]
#[cfg(any(
feature = "_tls-rustls-ring-webpki",
feature = "_tls-rustls-ring-native-roots"
))]
let provider = Arc::new(rustls::crypto::ring::default_provider());
// Unwrapping is safe here because we use a default provider.
@ -127,8 +134,10 @@ where
.with_no_client_auth()
}
} else {
let mut cert_store = RootCertStore::empty();
cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
#[cfg(any(feature = "_tls-rustls-aws-lc-rs", feature = "_tls-rustls-ring-webpki"))]
let mut cert_store = certs_from_webpki();
#[cfg(feature = "_tls-rustls-ring-native-roots")]
let mut cert_store = certs_from_native_store();
if let Some(ca) = tls_config.root_cert_path {
let data = ca.data().await?;
@ -204,6 +213,28 @@ fn private_key_from_pem(pem: Vec<u8>) -> Result<PrivateKeyDer<'static>, Error> {
}
}
#[cfg(any(feature = "_tls-rustls-aws-lc-rs", feature = "_tls-rustls-ring-webpki"))]
fn certs_from_webpki() -> RootCertStore {
RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned())
}
#[cfg(feature = "_tls-rustls-ring-native-roots")]
fn certs_from_native_store() -> RootCertStore {
let mut root_cert_store = RootCertStore::empty();
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.into()) {
log::warn!("rustls failed to parse native certificate: {e:?}");
}
}
root_cert_store
}
#[derive(Debug)]
struct DummyTlsVerifier {
provider: Arc<CryptoProvider>,

View file

@ -16,7 +16,8 @@ _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-ring = ["sqlx-core/_tls-rustls-ring"]
_tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki"]
_tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots"]
_sqlite = []

View file

@ -19,7 +19,8 @@ _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-ring = ["sqlx-macros-core/_tls-rustls-ring"]
_tls-rustls-ring-webpki = ["sqlx-macros-core/_tls-rustls-ring-webpki"]
_tls-rustls-ring-native-roots = ["sqlx-macros-core/_tls-rustls-ring-native-roots"]
# SQLx features
derive = ["sqlx-macros-core/derive"]