diff --git a/Cargo.lock b/Cargo.lock index 61d2e7d7b6..72b2d54182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3946,6 +3946,7 @@ dependencies = [ "num-bigint", "rand", "rust_decimal", + "rustls", "serde", "serde_json", "sha2", diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index 7bb1744189..fb90284276 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -57,13 +57,25 @@ impl std::fmt::Display for CertificateInput { } } -pub struct TlsConfig<'a> { +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum TlsConfig<'a> { + RawTlsConfig(RawTlsConfig<'a>), + #[cfg(feature = "_tls-rustls")] + PrebuiltRustls { + config: &'a rustls::ClientConfig, + hostname: &'a str, + }, +} + +#[derive(Debug, Clone)] +pub struct RawTlsConfig<'a> { pub accept_invalid_certs: bool, pub accept_invalid_hostnames: bool, pub hostname: &'a str, - pub root_cert_path: Option<&'a CertificateInput>, - pub client_cert_path: Option<&'a CertificateInput>, - pub client_key_path: Option<&'a CertificateInput>, + pub root_cert: Option<&'a CertificateInput>, + pub client_cert: Option<&'a CertificateInput>, + pub client_key: Option<&'a CertificateInput>, } pub async fn handshake( diff --git a/sqlx-core/src/net/tls/tls_native_tls.rs b/sqlx-core/src/net/tls/tls_native_tls.rs index 3423e48f8c..1631f6d8b4 100644 --- a/sqlx-core/src/net/tls/tls_native_tls.rs +++ b/sqlx-core/src/net/tls/tls_native_tls.rs @@ -2,6 +2,7 @@ use std::io::{self, Read, Write}; use crate::io::ReadBuf; use crate::net::tls::util::StdSocket; +use crate::net::tls::RawTlsConfig; use crate::net::tls::TlsConfig; use crate::net::Socket; use crate::rt; @@ -39,36 +40,56 @@ impl Socket for NativeTlsSocket { } } -pub async fn handshake( - socket: S, - config: TlsConfig<'_>, -) -> crate::Result> { - let mut builder = native_tls::TlsConnector::builder(); - - builder - .danger_accept_invalid_certs(config.accept_invalid_certs) - .danger_accept_invalid_hostnames(config.accept_invalid_hostnames); +impl TlsConfig<'_> { + async fn native_tls_connector(&self) -> crate::Result<(native_tls::TlsConnector, &str), Error> { + #[allow(irrefutable_let_patterns)] + let TlsConfig::RawTlsConfig(RawTlsConfig { + root_cert, + client_cert, + client_key, + accept_invalid_certs, + accept_invalid_hostnames, + hostname, + }) = self + else { + unreachable!() + }; + let mut builder = native_tls::TlsConnector::builder(); + + builder + .danger_accept_invalid_certs(*accept_invalid_certs) + .danger_accept_invalid_hostnames(*accept_invalid_hostnames); + + if let Some(root_cert) = root_cert { + let data = root_cert.data().await?; + builder.add_root_certificate( + native_tls::Certificate::from_pem(&data).map_err(Error::tls)?, + ); + } - if let Some(root_cert_path) = config.root_cert_path { - let data = root_cert_path.data().await?; - builder.add_root_certificate(native_tls::Certificate::from_pem(&data).map_err(Error::tls)?); - } + // authentication using user's key-file and its associated certificate + if let (Some(cert), Some(key)) = (client_cert, client_key) { + let cert = cert.data().await?; + let key = key.data().await?; + let identity = Identity::from_pkcs8(&cert, &key).map_err(Error::tls)?; + builder.identity(identity); + } - // authentication using user's key-file and its associated certificate - if let (Some(cert_path), Some(key_path)) = (config.client_cert_path, config.client_key_path) { - let cert_path = cert_path.data().await?; - let key_path = key_path.data().await?; - let identity = Identity::from_pkcs8(&cert_path, &key_path).map_err(Error::tls)?; - builder.identity(identity); + // The openssl TlsConnector synchronously loads certificates from files. + // Loading these files can block for tens of milliseconds. + let connector = rt::spawn_blocking(move || builder.build()) + .await + .map_err(Error::tls)?; + Ok((connector, hostname)) } +} - // The openssl TlsConnector synchronously loads certificates from files. - // Loading these files can block for tens of milliseconds. - let connector = rt::spawn_blocking(move || builder.build()) - .await - .map_err(Error::tls)?; - - let mut mid_handshake = match connector.connect(config.hostname, StdSocket::new(socket)) { +pub async fn handshake( + socket: S, + config: TlsConfig<'_>, +) -> crate::Result> { + let (connector, hostname) = config.native_tls_connector().await?; + let mut mid_handshake = match connector.connect(hostname, StdSocket::new(socket)) { Ok(tls_stream) => return Ok(NativeTlsSocket { stream: tls_stream }), Err(HandshakeError::Failure(e)) => return Err(Error::tls(e)), Err(HandshakeError::WouldBlock(mid_handshake)) => mid_handshake, diff --git a/sqlx-core/src/net/tls/tls_rustls.rs b/sqlx-core/src/net/tls/tls_rustls.rs index 1ecbbad519..12833e6717 100644 --- a/sqlx-core/src/net/tls/tls_rustls.rs +++ b/sqlx-core/src/net/tls/tls_rustls.rs @@ -19,7 +19,7 @@ use rustls::{ use crate::error::Error; use crate::io::ReadBuf; use crate::net::tls::util::StdSocket; -use crate::net::tls::TlsConfig; +use crate::net::tls::{RawTlsConfig, TlsConfig}; use crate::net::Socket; pub struct RustlsSocket { @@ -87,100 +87,135 @@ impl Socket for RustlsSocket { } } -pub async fn handshake(socket: S, tls_config: TlsConfig<'_>) -> Result, Error> -where - S: Socket, -{ - #[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(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. - let config = ClientConfig::builder_with_provider(provider.clone()) +impl TlsConfig<'_> { + async fn rustls_config(&self) -> crate::Result<(rustls::ClientConfig, &str), Error> { + let RawTlsConfig { + accept_invalid_certs, + accept_invalid_hostnames, + hostname, + root_cert, + client_cert, + client_key, + } = match self { + TlsConfig::RawTlsConfig(raw) => raw, + TlsConfig::PrebuiltRustls { config, hostname } => { + return Ok(((*config).to_owned(), hostname)); + } + }; + + #[cfg(all( + feature = "_tls-rustls-aws-lc-rs", + not(feature = "_tls-rustls-ring-webpki"), + not(feature = "_tls-rustls-ring-native-roots") + ))] + let config = ClientConfig::builder_with_provider(Arc::new( + rustls::crypto::aws_lc_rs::default_provider(), + )) .with_safe_default_protocol_versions() .unwrap(); + #[cfg(any( + feature = "_tls-rustls-ring-webpki", + feature = "_tls-rustls-ring-native-roots" + ))] + let config = + ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())) + .with_safe_default_protocol_versions() + .unwrap(); + #[cfg(all( + not(feature = "_tls-rustls-aws-lc-rs"), + not(feature = "_tls-rustls-ring-webpki"), + not(feature = "_tls-rustls-ring-native-roots") + ))] + let config = ClientConfig::builder(); + + // authentication using user's key and its associated certificate + let user_auth = match (client_cert, client_key) { + (Some(cert), Some(key)) => { + let cert_chain = certs_from_pem(cert.data().await?)?; + let key_der = private_key_from_pem(key.data().await?)?; + Some((cert_chain, key_der)) + } + (None, None) => None, + (_, _) => { + return Err(Error::Configuration( + "user auth key and certs must be given together".into(), + )) + } + }; - // authentication using user's key and its associated certificate - let user_auth = match (tls_config.client_cert_path, tls_config.client_key_path) { - (Some(cert_path), Some(key_path)) => { - let cert_chain = certs_from_pem(cert_path.data().await?)?; - let key_der = private_key_from_pem(key_path.data().await?)?; - Some((cert_chain, key_der)) - } - (None, None) => None, - (_, _) => { - return Err(Error::Configuration( - "user auth key and certs must be given together".into(), - )) - } - }; + let provider = config.crypto_provider().clone(); - let config = if tls_config.accept_invalid_certs { - if let Some(user_auth) = user_auth { - config - .dangerous() - .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) - .with_client_auth_cert(user_auth.0, user_auth.1) - .map_err(Error::tls)? + let config = if *accept_invalid_certs { + if let Some(user_auth) = user_auth { + config + .dangerous() + .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) + .with_client_auth_cert(user_auth.0, user_auth.1) + .map_err(Error::tls)? + } else { + config + .dangerous() + .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) + .with_no_client_auth() + } } else { - config - .dangerous() - .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) - .with_no_client_auth() - } - } else { - let mut cert_store = import_root_certs(); + let mut cert_store = import_root_certs(); - if let Some(ca) = tls_config.root_cert_path { - let data = ca.data().await?; + if let Some(ca) = root_cert { + 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())); - }; + for result in CertificateDer::pem_slice_iter(&data) { + let Ok(cert) = result else { + return Err(Error::Tls(format!("Invalid certificate {ca}").into())); + }; - cert_store.add(cert).map_err(|err| Error::Tls(err.into()))?; + cert_store.add(cert).map_err(|err| Error::Tls(err.into()))?; + } } - } - - 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 { + if *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 { config - .dangerous() - .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) + .with_root_certificates(cert_store) .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_root_certificates(cert_store) .with_no_client_auth() } - } else if let Some(user_auth) = user_auth { - config - .with_root_certificates(cert_store) - .with_client_auth_cert(user_auth.0, user_auth.1) - .map_err(Error::tls)? - } else { - config - .with_root_certificates(cert_store) - .with_no_client_auth() - } - }; + }; + + Ok((config, hostname)) + } +} - let host = ServerName::try_from(tls_config.hostname.to_owned()).map_err(Error::tls)?; +pub async fn handshake(socket: S, tls_config: TlsConfig<'_>) -> Result, Error> +where + S: Socket, +{ + let (config, hostname) = tls_config.rustls_config().await?; + let host = ServerName::try_from(hostname.to_owned()).map_err(Error::tls)?; let mut socket = RustlsSocket { inner: StdSocket::new(socket), diff --git a/sqlx-mysql/src/connection/tls.rs b/sqlx-mysql/src/connection/tls.rs index 9034fbd63a..8131b78308 100644 --- a/sqlx-mysql/src/connection/tls.rs +++ b/sqlx-mysql/src/connection/tls.rs @@ -1,3 +1,5 @@ +use sqlx_core::net::tls::RawTlsConfig; + use crate::connection::{MySqlStream, Waiting}; use crate::error::Error; use crate::net::tls::TlsConfig; @@ -53,17 +55,17 @@ pub(super) async fn maybe_upgrade( } } - let tls_config = TlsConfig { + let tls_config = TlsConfig::RawTlsConfig(RawTlsConfig { accept_invalid_certs: !matches!( options.ssl_mode, MySqlSslMode::VerifyCa | MySqlSslMode::VerifyIdentity ), accept_invalid_hostnames: !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity), hostname: &options.host, - root_cert_path: options.ssl_ca.as_ref(), - client_cert_path: options.ssl_client_cert.as_ref(), - client_key_path: options.ssl_client_key.as_ref(), - }; + root_cert: options.ssl_ca.as_ref(), + client_cert: options.ssl_client_cert.as_ref(), + client_key: options.ssl_client_key.as_ref(), + }); // Request TLS upgrade stream.write_packet(SslRequest { diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml index a70fb37d72..f2139511d8 100644 --- a/sqlx-postgres/Cargo.toml +++ b/sqlx-postgres/Cargo.toml @@ -14,6 +14,7 @@ any = ["sqlx-core/any"] json = ["sqlx-core/json"] migrate = ["sqlx-core/migrate"] offline = ["sqlx-core/offline"] +rustls = ["dep:rustls", "sqlx-core/_tls-rustls"] # Type Integration features bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "sqlx-core/bigdecimal"] @@ -27,6 +28,9 @@ time = ["dep:time", "sqlx-core/time"] uuid = ["dep:uuid", "sqlx-core/uuid"] [dependencies] +# TLS +rustls = { version = "0.23.24", default-features = false, features = ["std", "tls12"], optional = true } + # Futures crates futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } futures-core = { version = "0.3.19", default-features = false } diff --git a/sqlx-postgres/src/connection/tls.rs b/sqlx-postgres/src/connection/tls.rs index a49c9caa8c..db9da857b5 100644 --- a/sqlx-postgres/src/connection/tls.rs +++ b/sqlx-postgres/src/connection/tls.rs @@ -1,8 +1,11 @@ +use sqlx_core::net::tls::RawTlsConfig; + use crate::error::Error; use crate::net::tls::{self, TlsConfig}; use crate::net::{Socket, SocketIntoBox, WithSocket}; use crate::message::SslRequest; +use crate::options::SslConfig; use crate::{PgConnectOptions, PgSslMode}; pub struct MaybeUpgradeTls<'a>(pub &'a PgConnectOptions); @@ -45,19 +48,28 @@ async fn maybe_upgrade( } } - let accept_invalid_certs = !matches!( - options.ssl_mode, - PgSslMode::VerifyCa | PgSslMode::VerifyFull - ); - let accept_invalid_hostnames = !matches!(options.ssl_mode, PgSslMode::VerifyFull); - - let config = TlsConfig { - accept_invalid_certs, - accept_invalid_hostnames, - hostname: &options.host, - root_cert_path: options.ssl_root_cert.as_ref(), - client_cert_path: options.ssl_client_cert.as_ref(), - client_key_path: options.ssl_client_key.as_ref(), + let config = match &options.ssl_config { + SslConfig::Libpq(c) => { + let accept_invalid_certs = !matches!( + options.ssl_mode, + PgSslMode::VerifyCa | PgSslMode::VerifyFull + ); + let accept_invalid_hostnames = !matches!(options.ssl_mode, PgSslMode::VerifyFull); + + TlsConfig::RawTlsConfig(RawTlsConfig { + accept_invalid_certs, + accept_invalid_hostnames, + hostname: &options.host, + root_cert: c.root_cert.as_ref(), + client_cert: c.client_cert.as_ref(), + client_key: c.client_key.as_ref(), + }) + } + #[cfg(feature = "rustls")] + SslConfig::Rustls(config) => TlsConfig::PrebuiltRustls { + config, + hostname: &options.host, + }, }; tls::handshake(socket, config, SocketIntoBox).await diff --git a/sqlx-postgres/src/options/mod.rs b/sqlx-postgres/src/options/mod.rs index efbc43989b..10d7e442ac 100644 --- a/sqlx-postgres/src/options/mod.rs +++ b/sqlx-postgres/src/options/mod.rs @@ -22,9 +22,7 @@ pub struct PgConnectOptions { pub(crate) password: Option, pub(crate) database: Option, pub(crate) ssl_mode: PgSslMode, - pub(crate) ssl_root_cert: Option, - pub(crate) ssl_client_cert: Option, - pub(crate) ssl_client_key: Option, + pub(crate) ssl_config: SslConfig, pub(crate) statement_cache_capacity: usize, pub(crate) application_name: Option, pub(crate) log_settings: LogSettings, @@ -32,6 +30,20 @@ pub struct PgConnectOptions { pub(crate) options: Option, } +#[derive(Debug, Clone)] +pub(crate) enum SslConfig { + Libpq(LibpqSslConfig), + #[cfg(feature = "rustls")] + Rustls(rustls::ClientConfig), +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct LibpqSslConfig { + pub(crate) root_cert: Option, + pub(crate) client_cert: Option, + pub(crate) client_key: Option, +} + impl Default for PgConnectOptions { fn default() -> Self { Self::new_without_pgpass().apply_pgpass() @@ -68,6 +80,15 @@ impl PgConnectOptions { let database = var("PGDATABASE").ok(); + let ssl_config = SslConfig::Libpq(LibpqSslConfig { + root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), + client_cert: var("PGSSLCERT").ok().map(CertificateInput::from), + // As of writing, the implementation of `From` only looks for + // `-----BEGIN CERTIFICATE-----` and so will not attempt to parse + // a PEM-encoded private key. + client_key: var("PGSSLKEY").ok().map(CertificateInput::from), + }); + PgConnectOptions { port, host, @@ -75,12 +96,7 @@ impl PgConnectOptions { username, password: var("PGPASSWORD").ok(), database, - ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), - ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from), - // As of writing, the implementation of `From` only looks for - // `-----BEGIN CERTIFICATE-----` and so will not attempt to parse - // a PEM-encoded private key. - ssl_client_key: var("PGSSLKEY").ok().map(CertificateInput::from), + ssl_config, ssl_mode: var("PGSSLMODE") .ok() .and_then(|v| v.parse().ok()) @@ -232,7 +248,16 @@ impl PgConnectOptions { /// .ssl_root_cert("./ca-certificate.crt"); /// ``` pub fn ssl_root_cert(mut self, cert: impl AsRef) -> Self { - self.ssl_root_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + let cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(c) = &mut self.ssl_config { + c.root_cert = cert; + } else { + self.ssl_config = SslConfig::Libpq(LibpqSslConfig { + root_cert: cert, + ..Default::default() + }); + } self } @@ -248,7 +273,16 @@ impl PgConnectOptions { /// .ssl_client_cert("./client.crt"); /// ``` pub fn ssl_client_cert(mut self, cert: impl AsRef) -> Self { - self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + let cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(c) = &mut self.ssl_config { + c.client_cert = cert; + } else { + self.ssl_config = SslConfig::Libpq(LibpqSslConfig { + client_cert: cert, + ..Default::default() + }); + } self } @@ -267,14 +301,23 @@ impl PgConnectOptions { /// -----BEGIN CERTIFICATE----- /// /// -----END CERTIFICATE-----"; - /// + /// /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_client_cert_from_pem(CERT); /// ``` pub fn ssl_client_cert_from_pem(mut self, cert: impl AsRef<[u8]>) -> Self { - self.ssl_client_cert = Some(CertificateInput::Inline(cert.as_ref().to_vec())); + let cert = Some(CertificateInput::Inline(cert.as_ref().to_vec())); + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(c) = &mut self.ssl_config { + c.client_cert = cert; + } else { + self.ssl_config = SslConfig::Libpq(LibpqSslConfig { + client_cert: cert, + ..Default::default() + }); + } self } @@ -290,7 +333,16 @@ impl PgConnectOptions { /// .ssl_client_key("./client.key"); /// ``` pub fn ssl_client_key(mut self, key: impl AsRef) -> Self { - self.ssl_client_key = Some(CertificateInput::File(key.as_ref().to_path_buf())); + let key = Some(CertificateInput::File(key.as_ref().to_path_buf())); + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(c) = &mut self.ssl_config { + c.client_key = key; + } else { + self.ssl_config = SslConfig::Libpq(LibpqSslConfig { + client_key: key, + ..Default::default() + }); + } self } @@ -316,7 +368,16 @@ impl PgConnectOptions { /// .ssl_client_key_from_pem(KEY); /// ``` pub fn ssl_client_key_from_pem(mut self, key: impl AsRef<[u8]>) -> Self { - self.ssl_client_key = Some(CertificateInput::Inline(key.as_ref().to_vec())); + let key = Some(CertificateInput::Inline(key.as_ref().to_vec())); + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(c) = &mut self.ssl_config { + c.client_key = key; + } else { + self.ssl_config = SslConfig::Libpq(LibpqSslConfig { + client_key: key, + ..Default::default() + }); + } self } @@ -332,7 +393,16 @@ impl PgConnectOptions { /// .ssl_root_cert_from_pem(vec![]); /// ``` pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec) -> Self { - self.ssl_root_cert = Some(CertificateInput::Inline(pem_certificate)); + let cert = Some(CertificateInput::Inline(pem_certificate)); + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(c) = &mut self.ssl_config { + c.root_cert = cert; + } else { + self.ssl_config = SslConfig::Libpq(LibpqSslConfig { + root_cert: cert, + ..Default::default() + }); + } self } diff --git a/sqlx-postgres/src/options/parse.rs b/sqlx-postgres/src/options/parse.rs index e911305698..eccca6bc53 100644 --- a/sqlx-postgres/src/options/parse.rs +++ b/sqlx-postgres/src/options/parse.rs @@ -1,4 +1,5 @@ use crate::error::Error; +use crate::options::{LibpqSslConfig, SslConfig}; use crate::{PgConnectOptions, PgSslMode}; use sqlx_core::percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; use sqlx_core::Url; @@ -146,19 +147,28 @@ impl PgConnectOptions { }; url.query_pairs_mut().append_pair("sslmode", ssl_mode); - if let Some(ssl_root_cert) = &self.ssl_root_cert { - url.query_pairs_mut() - .append_pair("sslrootcert", &ssl_root_cert.to_string()); - } + // If the `rustls` feature is enabled, it isn't irrefutable. + #[allow(irrefutable_let_patterns)] + if let SslConfig::Libpq(LibpqSslConfig { + root_cert, + client_cert, + client_key, + }) = &self.ssl_config + { + if let Some(root_cert) = root_cert { + url.query_pairs_mut() + .append_pair("sslrootcert", &root_cert.to_string()); + } - if let Some(ssl_client_cert) = &self.ssl_client_cert { - url.query_pairs_mut() - .append_pair("sslcert", &ssl_client_cert.to_string()); - } + if let Some(client_cert) = client_cert { + url.query_pairs_mut() + .append_pair("sslcert", &client_cert.to_string()); + } - if let Some(ssl_client_key) = &self.ssl_client_key { - url.query_pairs_mut() - .append_pair("sslkey", &ssl_client_key.to_string()); + if let Some(client_key) = client_key { + url.query_pairs_mut() + .append_pair("sslkey", &client_key.to_string()); + } } url.query_pairs_mut().append_pair(