1use crate::config::DatabaseConfig;
13use rustlavel_core::{Error, Result};
14use std::sync::Arc;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum TlsMode {
23 Disable,
26 #[default]
35 Prefer,
36 Require,
42 VerifyCa,
49 VerifyFull,
52}
53
54impl TlsMode {
55 pub fn parse(raw: &str) -> Result<TlsMode> {
56 Ok(match raw.trim().to_ascii_lowercase().replace('_', "-").as_str() {
57 "disable" | "disabled" | "off" | "false" => TlsMode::Disable,
58 "prefer" | "preferred" => TlsMode::Prefer,
59 "require" | "required" | "on" | "true" => TlsMode::Require,
60 "verify-ca" => TlsMode::VerifyCa,
61 "verify-full" | "verify-identity" => TlsMode::VerifyFull,
62 other => {
63 return Err(Error::msg(format!(
64 "`{other}` is not an sslmode. Use disable, prefer, require, verify-ca or \
65 verify-full — prefer is the default, and verify-full is the only one that \
66 is safe against an active attacker."
67 )));
68 }
69 })
70 }
71
72 pub fn as_str(self) -> &'static str {
73 match self {
74 TlsMode::Disable => "disable",
75 TlsMode::Prefer => "prefer",
76 TlsMode::Require => "require",
77 TlsMode::VerifyCa => "verify-ca",
78 TlsMode::VerifyFull => "verify-full",
79 }
80 }
81
82 pub fn wants_tls(self) -> bool {
84 self != TlsMode::Disable
85 }
86
87 pub fn demands_tls(self) -> bool {
89 matches!(self, TlsMode::Require | TlsMode::VerifyCa | TlsMode::VerifyFull)
90 }
91
92 pub fn verifies_certificate(self) -> bool {
94 matches!(self, TlsMode::VerifyCa | TlsMode::VerifyFull)
95 }
96}
97
98impl std::fmt::Display for TlsMode {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.write_str(self.as_str())
101 }
102}
103
104pub enum DbStream {
113 Plain(tokio::net::TcpStream),
114 Tls(Box<tokio_rustls::client::TlsStream<tokio::net::TcpStream>>),
115 Closed,
116}
117
118impl DbStream {
119 pub fn is_encrypted(&self) -> bool {
120 matches!(self, DbStream::Tls(_))
121 }
122
123 pub fn take_plain(&mut self) -> Result<tokio::net::TcpStream> {
128 match std::mem::replace(self, DbStream::Closed) {
129 DbStream::Plain(stream) => Ok(stream),
130 DbStream::Tls(stream) => {
131 *self = DbStream::Tls(stream);
132 Err(Error::msg("this connection is already encrypted"))
133 }
134 DbStream::Closed => Err(closed()),
135 }
136 }
137
138 pub async fn write_all(&mut self, bytes: &[u8]) -> std::io::Result<()> {
139 use tokio::io::AsyncWriteExt;
140 match self {
141 DbStream::Plain(stream) => stream.write_all(bytes).await,
142 DbStream::Tls(stream) => stream.write_all(bytes).await,
143 DbStream::Closed => Err(closed_io()),
144 }
145 }
146
147 pub async fn flush(&mut self) -> std::io::Result<()> {
148 use tokio::io::AsyncWriteExt;
149 match self {
150 DbStream::Plain(stream) => stream.flush().await,
151 DbStream::Tls(stream) => stream.flush().await,
152 DbStream::Closed => Err(closed_io()),
153 }
154 }
155
156 pub async fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
157 use tokio::io::AsyncReadExt;
158 match self {
159 DbStream::Plain(stream) => stream.read(buffer).await,
160 DbStream::Tls(stream) => stream.read(buffer).await,
161 DbStream::Closed => Err(closed_io()),
162 }
163 }
164
165 pub async fn shutdown(&mut self) -> std::io::Result<()> {
166 use tokio::io::AsyncWriteExt;
167 match self {
168 DbStream::Plain(stream) => stream.shutdown().await,
169 DbStream::Tls(stream) => stream.shutdown().await,
170 DbStream::Closed => Ok(()),
171 }
172 }
173}
174
175fn closed() -> Error {
176 Error::msg("the connection was left mid-upgrade; this is a bug in the driver")
177}
178
179fn closed_io() -> std::io::Error {
180 std::io::Error::other("the connection was left mid-upgrade; this is a bug in the driver")
181}
182
183pub async fn upgrade(
185 stream: tokio::net::TcpStream,
186 host: &str,
187 config: &DatabaseConfig,
188) -> Result<tokio_rustls::client::TlsStream<tokio::net::TcpStream>> {
189 let connector = tokio_rustls::TlsConnector::from(client_config(config)?);
190
191 let name = rustls::pki_types::ServerName::try_from(host.to_string())
195 .map_err(|_| Error::msg(format!("`{host}` is not a valid TLS server name")))?;
196
197 connector.connect(name, stream).await.map_err(|error| {
198 Error::msg(format!(
199 "the TLS handshake with {host} failed: {error}. sslmode is `{}`; if this is a \
200 development server with a self-signed certificate, either point `sslrootcert` at \
201 its certificate or drop to sslmode=require.",
202 config.tls_mode
203 ))
204 })
205}
206
207fn client_config(config: &DatabaseConfig) -> Result<Arc<rustls::ClientConfig>> {
209 let roots = match &config.tls_root_certificate {
210 Some(path) => root_store_from_pem(path)?,
211 None => rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() },
212 };
213
214 let builder = rustls::ClientConfig::builder();
215
216 let config = match config.tls_mode {
217 TlsMode::Require | TlsMode::Disable | TlsMode::Prefer => builder
220 .dangerous()
221 .with_custom_certificate_verifier(Arc::new(TrustAnyCertificate::new()?))
222 .with_no_client_auth(),
223 TlsMode::VerifyCa => {
224 let verifier = rustls::client::WebPkiServerVerifier::builder(Arc::new(roots))
225 .build()
226 .map_err(|error| Error::msg(format!("could not build a certificate verifier: {error}")))?;
227 builder
228 .dangerous()
229 .with_custom_certificate_verifier(Arc::new(ChainOnly(verifier)))
230 .with_no_client_auth()
231 }
232 TlsMode::VerifyFull => builder.with_root_certificates(roots).with_no_client_auth(),
233 };
234
235 Ok(Arc::new(config))
236}
237
238fn root_store_from_pem(path: &str) -> Result<rustls::RootCertStore> {
244 let text = std::fs::read_to_string(path).map_err(|error| {
245 Error::msg(format!("could not read the certificate at `{path}`: {error}"))
246 })?;
247
248 let mut store = rustls::RootCertStore::empty();
249 let mut found = 0;
250
251 for block in text.split("-----BEGIN CERTIFICATE-----").skip(1) {
252 let body = block.split("-----END CERTIFICATE-----").next().ok_or_else(|| {
253 Error::msg(format!("`{path}` has a BEGIN CERTIFICATE line with no END"))
254 })?;
255
256 let der = crate::base64::decode(&body.replace([' ', '\t'], "")).ok_or_else(|| {
257 Error::msg(format!("`{path}` contains a certificate that is not valid base64"))
258 })?;
259
260 store.add(rustls::pki_types::CertificateDer::from(der)).map_err(|error| {
261 Error::msg(format!("`{path}` contains a certificate rustls will not accept: {error}"))
262 })?;
263 found += 1;
264 }
265
266 if found == 0 {
267 return Err(Error::msg(format!(
268 "`{path}` contains no `-----BEGIN CERTIFICATE-----` block. It should be a PEM \
269 file; a DER file has to be converted first."
270 )));
271 }
272
273 Ok(store)
274}
275
276#[derive(Debug)]
283struct ChainOnly(Arc<rustls::client::WebPkiServerVerifier>);
284
285impl rustls::client::danger::ServerCertVerifier for ChainOnly {
286 fn verify_server_cert(
287 &self,
288 end_entity: &rustls::pki_types::CertificateDer<'_>,
289 intermediates: &[rustls::pki_types::CertificateDer<'_>],
290 server_name: &rustls::pki_types::ServerName<'_>,
291 ocsp_response: &[u8],
292 now: rustls::pki_types::UnixTime,
293 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
294 use rustls::CertificateError::{NotValidForName, NotValidForNameContext};
295
296 match self.0.verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
297 {
298 Err(rustls::Error::InvalidCertificate(
299 NotValidForName | NotValidForNameContext { .. },
300 )) => Ok(rustls::client::danger::ServerCertVerified::assertion()),
301 other => other,
302 }
303 }
304
305 fn verify_tls12_signature(
306 &self,
307 message: &[u8],
308 cert: &rustls::pki_types::CertificateDer<'_>,
309 dss: &rustls::DigitallySignedStruct,
310 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
311 self.0.verify_tls12_signature(message, cert, dss)
312 }
313
314 fn verify_tls13_signature(
315 &self,
316 message: &[u8],
317 cert: &rustls::pki_types::CertificateDer<'_>,
318 dss: &rustls::DigitallySignedStruct,
319 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
320 self.0.verify_tls13_signature(message, cert, dss)
321 }
322
323 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
324 self.0.supported_verify_schemes()
325 }
326}
327
328#[derive(Debug)]
330struct TrustAnyCertificate(Arc<rustls::crypto::CryptoProvider>);
331
332impl TrustAnyCertificate {
333 fn new() -> Result<TrustAnyCertificate> {
334 let provider = rustls::crypto::CryptoProvider::get_default()
335 .cloned()
336 .unwrap_or_else(|| Arc::new(rustls::crypto::aws_lc_rs::default_provider()));
337 Ok(TrustAnyCertificate(provider))
338 }
339}
340
341impl rustls::client::danger::ServerCertVerifier for TrustAnyCertificate {
342 fn verify_server_cert(
343 &self,
344 _end_entity: &rustls::pki_types::CertificateDer<'_>,
345 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
346 _server_name: &rustls::pki_types::ServerName<'_>,
347 _ocsp_response: &[u8],
348 _now: rustls::pki_types::UnixTime,
349 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
350 Ok(rustls::client::danger::ServerCertVerified::assertion())
351 }
352
353 fn verify_tls12_signature(
354 &self,
355 message: &[u8],
356 cert: &rustls::pki_types::CertificateDer<'_>,
357 dss: &rustls::DigitallySignedStruct,
358 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
359 rustls::crypto::verify_tls12_signature(
360 message,
361 cert,
362 dss,
363 &self.0.signature_verification_algorithms,
364 )
365 }
366
367 fn verify_tls13_signature(
368 &self,
369 message: &[u8],
370 cert: &rustls::pki_types::CertificateDer<'_>,
371 dss: &rustls::DigitallySignedStruct,
372 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
373 rustls::crypto::verify_tls13_signature(
374 message,
375 cert,
376 dss,
377 &self.0.signature_verification_algorithms,
378 )
379 }
380
381 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
382 self.0.signature_verification_algorithms.supported_schemes()
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn modes_parse_under_the_spellings_people_actually_write() {
392 assert_eq!(TlsMode::parse("disable").unwrap(), TlsMode::Disable);
393 assert_eq!(TlsMode::parse("PREFER").unwrap(), TlsMode::Prefer);
394 assert_eq!(TlsMode::parse(" require ").unwrap(), TlsMode::Require);
395 assert_eq!(TlsMode::parse("verify_ca").unwrap(), TlsMode::VerifyCa);
396 assert_eq!(TlsMode::parse("verify-full").unwrap(), TlsMode::VerifyFull);
397 assert_eq!(TlsMode::parse("VERIFY_IDENTITY").unwrap(), TlsMode::VerifyFull);
399 }
400
401 #[test]
402 fn an_unknown_mode_lists_the_real_ones_and_says_which_is_safe() {
403 let error = TlsMode::parse("yes-please").unwrap_err().to_string();
404
405 assert!(error.contains("verify-full"), "got {error}");
406 assert!(error.contains("active attacker"), "the error should say what is at stake");
407 }
408
409 #[test]
410 fn the_default_is_prefer() {
411 assert_eq!(TlsMode::default(), TlsMode::Prefer);
412 }
413
414 #[test]
415 fn what_each_mode_insists_on() {
416 assert!(TlsMode::Prefer.wants_tls());
419 assert!(!TlsMode::Prefer.demands_tls());
420 assert!(!TlsMode::Prefer.verifies_certificate());
421
422 assert!(!TlsMode::Disable.wants_tls());
423
424 assert!(TlsMode::Require.demands_tls());
425 assert!(!TlsMode::Require.verifies_certificate());
426
427 for mode in [TlsMode::VerifyCa, TlsMode::VerifyFull] {
428 assert!(mode.demands_tls());
429 assert!(mode.verifies_certificate());
430 }
431 }
432
433 #[test]
434 fn modes_round_trip_through_their_written_form() {
435 for mode in [
436 TlsMode::Disable,
437 TlsMode::Prefer,
438 TlsMode::Require,
439 TlsMode::VerifyCa,
440 TlsMode::VerifyFull,
441 ] {
442 assert_eq!(TlsMode::parse(mode.as_str()).unwrap(), mode);
443 }
444 }
445
446 #[test]
447 fn a_pem_file_that_is_not_a_certificate_says_so() {
448 let path = std::env::temp_dir().join(format!("rustlavel-tls-not-a-cert-{}.pem", std::process::id()));
449 std::fs::write(&path, "just some text\n").unwrap();
450
451 let error = root_store_from_pem(path.to_str().unwrap()).unwrap_err().to_string();
452 assert!(error.contains("BEGIN CERTIFICATE"), "got {error}");
453
454 let _ = std::fs::remove_file(&path);
455 }
456
457 #[test]
458 fn a_missing_certificate_file_names_the_path() {
459 let error = root_store_from_pem("/nope/does-not-exist.pem").unwrap_err().to_string();
460 assert!(error.contains("/nope/does-not-exist.pem"), "got {error}");
461 }
462
463 #[test]
464 fn a_stream_left_mid_upgrade_errors_rather_than_hanging() {
465 let mut stream = DbStream::Closed;
466 assert!(stream.take_plain().is_err());
467 assert!(!stream.is_encrypted());
468 }
469}