Skip to main content

sqlmodel_postgres/
tls.rs

1//! TLS support for PostgreSQL connections (feature-gated).
2//!
3//! PostgreSQL TLS is negotiated by sending an `SSLRequest` message and then
4//! upgrading the underlying TCP stream to a TLS stream using rustls.
5
6#[cfg(feature = "tls")]
7use sqlmodel_core::Error;
8#[cfg(feature = "tls")]
9use sqlmodel_core::error::{ConnectionError, ConnectionErrorKind};
10
11#[cfg(feature = "tls")]
12use crate::config::SslMode;
13
14#[cfg(feature = "tls")]
15use std::sync::Arc;
16
17#[cfg(feature = "tls")]
18fn tls_error(message: impl Into<String>) -> Error {
19    Error::Connection(ConnectionError {
20        kind: ConnectionErrorKind::Ssl,
21        message: message.into(),
22        source: None,
23    })
24}
25
26// `sqlmodel_core::Error` is ~160 bytes; it is the crate-wide error type, so
27// boxing it just for these helpers would fragment the error surface.
28#[allow(clippy::result_large_err)]
29#[cfg(feature = "tls")]
30pub(crate) fn server_name(host: &str) -> Result<rustls::pki_types::ServerName<'static>, Error> {
31    host.to_string()
32        .try_into()
33        .map_err(|e| tls_error(format!("Invalid server name '{host}': {e}")))
34}
35
36/// Build a rustls ClientConfig based on PostgreSQL SSL mode.
37///
38/// Semantics:
39/// - Disable: not applicable (should not call)
40/// - Prefer/Require: encrypt, do not verify certificates
41/// - VerifyCa/VerifyFull: verify against webpki-roots CA bundle
42#[allow(clippy::result_large_err)]
43#[cfg(feature = "tls")]
44pub(crate) fn build_client_config(ssl_mode: SslMode) -> Result<rustls::ClientConfig, Error> {
45    let provider = Arc::new(rustls::crypto::ring::default_provider());
46
47    match ssl_mode {
48        SslMode::Disable => Err(tls_error("TLS config requested with SslMode::Disable")),
49        SslMode::Prefer | SslMode::Require => build_no_verify_config(&provider),
50        SslMode::VerifyCa | SslMode::VerifyFull => build_webpki_config(&provider),
51    }
52}
53
54/// Build a ClientConfig that skips certificate verification (dangerous!).
55#[allow(clippy::result_large_err)]
56#[cfg(feature = "tls")]
57fn build_no_verify_config(
58    provider: &Arc<rustls::crypto::CryptoProvider>,
59) -> Result<rustls::ClientConfig, Error> {
60    use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
61    use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
62    use rustls::{DigitallySignedStruct, Error as RustlsError, SignatureScheme};
63
64    #[derive(Debug)]
65    struct NoVerifier;
66
67    impl ServerCertVerifier for NoVerifier {
68        fn verify_server_cert(
69            &self,
70            _end_entity: &CertificateDer<'_>,
71            _intermediates: &[CertificateDer<'_>],
72            _server_name: &ServerName<'_>,
73            _ocsp_response: &[u8],
74            _now: UnixTime,
75        ) -> Result<ServerCertVerified, RustlsError> {
76            Ok(ServerCertVerified::assertion())
77        }
78
79        fn verify_tls12_signature(
80            &self,
81            _message: &[u8],
82            _cert: &CertificateDer<'_>,
83            _dss: &DigitallySignedStruct,
84        ) -> Result<HandshakeSignatureValid, RustlsError> {
85            Ok(HandshakeSignatureValid::assertion())
86        }
87
88        fn verify_tls13_signature(
89            &self,
90            _message: &[u8],
91            _cert: &CertificateDer<'_>,
92            _dss: &DigitallySignedStruct,
93        ) -> Result<HandshakeSignatureValid, RustlsError> {
94            Ok(HandshakeSignatureValid::assertion())
95        }
96
97        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
98            vec![
99                SignatureScheme::RSA_PKCS1_SHA256,
100                SignatureScheme::RSA_PKCS1_SHA384,
101                SignatureScheme::RSA_PKCS1_SHA512,
102                SignatureScheme::ECDSA_NISTP256_SHA256,
103                SignatureScheme::ECDSA_NISTP384_SHA384,
104                SignatureScheme::ECDSA_NISTP521_SHA512,
105                SignatureScheme::RSA_PSS_SHA256,
106                SignatureScheme::RSA_PSS_SHA384,
107                SignatureScheme::RSA_PSS_SHA512,
108                SignatureScheme::ED25519,
109            ]
110        }
111    }
112
113    let config = rustls::ClientConfig::builder_with_provider(provider.clone())
114        .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
115        .map_err(|e| tls_error(format!("Failed to set TLS versions: {e}")))?
116        .dangerous()
117        .with_custom_certificate_verifier(Arc::new(NoVerifier))
118        .with_no_client_auth();
119
120    Ok(config)
121}
122
123/// Build a ClientConfig using webpki-roots CA bundle.
124#[allow(clippy::result_large_err)]
125#[cfg(feature = "tls")]
126fn build_webpki_config(
127    provider: &Arc<rustls::crypto::CryptoProvider>,
128) -> Result<rustls::ClientConfig, Error> {
129    use rustls::RootCertStore;
130
131    let mut root_store = RootCertStore::empty();
132    root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
133
134    let config = rustls::ClientConfig::builder_with_provider(provider.clone())
135        .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
136        .map_err(|e| tls_error(format!("Failed to set TLS versions: {e}")))?
137        .with_root_certificates(root_store)
138        .with_no_client_auth();
139
140    Ok(config)
141}