Skip to main content

smtp_test_tool/
tls.rs

1//! TLS configuration helpers built on rustls.
2
3use anyhow::{Context, Result};
4use rustls::pki_types::pem::PemObject;
5use rustls::pki_types::CertificateDer;
6use rustls::{ClientConfig, RootCertStore};
7use serde::{Deserialize, Serialize};
8use std::path::Path;
9use std::sync::Arc;
10
11/// Transport security mode for any of SMTP / IMAP / POP3.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Security {
15    /// Plain TCP, no encryption (rare and discouraged).
16    None,
17    /// Connect in cleartext then upgrade with STARTTLS / STLS.
18    #[serde(rename = "starttls")]
19    StartTls,
20    /// Implicit TLS on a dedicated port (465 SMTPS, 993 IMAPS, 995 POP3S).
21    #[serde(rename = "ssl")]
22    Implicit,
23}
24
25impl Security {
26    pub fn as_str(self) -> &'static str {
27        match self {
28            Security::None => "none",
29            Security::StartTls => "starttls",
30            Security::Implicit => "ssl",
31        }
32    }
33}
34
35/// Build a rustls `ClientConfig` using the bundled Mozilla root store, plus
36/// any extra CA bundle the user specified.  When `insecure` is true,
37/// certificate validation is disabled (test/diagnostic use only).
38pub fn build_client_config(ca_file: Option<&Path>, insecure: bool) -> Result<Arc<ClientConfig>> {
39    let mut roots = RootCertStore::empty();
40    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
41
42    if let Some(path) = ca_file {
43        // PemObject (in rustls-pki-types >=1.9) is the maintained
44        // replacement for the now-unmaintained rustls-pemfile crate
45        // (see RUSTSEC-2025-0134).  It loads CertificateDer entries
46        // straight from a PEM file with no extra dependency.
47        let certs: Vec<CertificateDer<'static>> = CertificateDer::pem_file_iter(path)
48            .with_context(|| format!("opening CA bundle {}", path.display()))?
49            .collect::<std::result::Result<Vec<_>, _>>()
50            .with_context(|| format!("parsing CA PEM {}", path.display()))?;
51        for cert in certs {
52            roots.add(cert).context("adding user CA to root store")?;
53        }
54    }
55
56    let config = if insecure {
57        // Danger: accept any server cert.  Documented in --help.
58        let mut cfg = ClientConfig::builder()
59            .with_root_certificates(roots)
60            .with_no_client_auth();
61        cfg.dangerous()
62            .set_certificate_verifier(Arc::new(danger::AcceptAnyCert));
63        cfg
64    } else {
65        ClientConfig::builder()
66            .with_root_certificates(roots)
67            .with_no_client_auth()
68    };
69
70    Ok(Arc::new(config))
71}
72
73// ---------- "insecure" verifier (test-only) -----------------------------
74mod danger {
75    use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
76    use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
77    use rustls::{DigitallySignedStruct, Error, SignatureScheme};
78
79    #[derive(Debug)]
80    pub struct AcceptAnyCert;
81
82    impl ServerCertVerifier for AcceptAnyCert {
83        fn verify_server_cert(
84            &self,
85            _end: &CertificateDer<'_>,
86            _ints: &[CertificateDer<'_>],
87            _sn: &ServerName<'_>,
88            _ocsp: &[u8],
89            _now: UnixTime,
90        ) -> Result<ServerCertVerified, Error> {
91            Ok(ServerCertVerified::assertion())
92        }
93
94        fn verify_tls12_signature(
95            &self,
96            _m: &[u8],
97            _c: &CertificateDer<'_>,
98            _d: &DigitallySignedStruct,
99        ) -> Result<HandshakeSignatureValid, Error> {
100            Ok(HandshakeSignatureValid::assertion())
101        }
102
103        fn verify_tls13_signature(
104            &self,
105            _m: &[u8],
106            _c: &CertificateDer<'_>,
107            _d: &DigitallySignedStruct,
108        ) -> Result<HandshakeSignatureValid, Error> {
109            Ok(HandshakeSignatureValid::assertion())
110        }
111
112        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
113            vec![
114                SignatureScheme::RSA_PKCS1_SHA256,
115                SignatureScheme::RSA_PKCS1_SHA384,
116                SignatureScheme::RSA_PKCS1_SHA512,
117                SignatureScheme::ECDSA_NISTP256_SHA256,
118                SignatureScheme::ECDSA_NISTP384_SHA384,
119                SignatureScheme::RSA_PSS_SHA256,
120                SignatureScheme::RSA_PSS_SHA384,
121                SignatureScheme::RSA_PSS_SHA512,
122                SignatureScheme::ED25519,
123            ]
124        }
125    }
126}