kumo_tls_helper/
lib.rs

1//! Shared TLS configuration helpers for KumoMTA.
2//!
3//! This crate provides TLS connector building and async stream traits
4//! that are shared across the KumoMTA crates.
5
6mod traits;
7
8pub use traits::*;
9
10use hickory_proto::rr::rdata::tlsa::{CertUsage, Matching, Selector};
11use hickory_proto::rr::rdata::TLSA;
12use openssl::pkey::PKey;
13use openssl::ssl::{DaneMatchType, DaneSelector, DaneUsage, SslOptions};
14use openssl::x509::X509;
15use rustls::pki_types::{CertificateDer, PrivateKeyDer};
16use rustls_pemfile::certs;
17use std::io::BufReader;
18use std::sync::Arc;
19use thiserror::Error;
20use tokio::time::{Duration, Instant};
21use tokio_rustls::rustls::client::danger::ServerCertVerifier;
22use tokio_rustls::rustls::crypto::{aws_lc_rs as provider, CryptoProvider};
23use tokio_rustls::rustls::{ClientConfig, SupportedCipherSuite};
24use tokio_rustls::TlsConnector;
25
26/// Errors that can occur when building an OpenSSL connector.
27#[derive(Error, Debug, Clone)]
28pub enum OpensslConnectorError {
29    #[error("SSL Error: {0}")]
30    SslErrorStack(String),
31    #[error("No usable DANE TLSA records for {hostname}: {tlsa:?}")]
32    NoUsableDaneTlsa { hostname: String, tlsa: Vec<TLSA> },
33}
34
35impl From<openssl::error::ErrorStack> for OpensslConnectorError {
36    fn from(err: openssl::error::ErrorStack) -> Self {
37        OpensslConnectorError::SslErrorStack(err.to_string())
38    }
39}
40
41#[derive(Clone, Debug)]
42struct RustlsCacheKey {
43    insecure: bool,
44    certificate_from_pem: Option<Arc<Box<[u8]>>>,
45    private_key_from_pem: Option<Arc<Box<[u8]>>>,
46    rustls_cipher_suites: Vec<SupportedCipherSuite>,
47}
48
49// SupportedCipherSuite has a PartialEq impl but not an Eq impl.
50// Since we need RustlsCacheKey to be Hash we cannot simply derive
51// PartialEq and then add an explicit impl for Eq on RustlsCacheKey
52// because we don't know the implementation details of the underlying
53// PartialEq impl. So we define our own here where we explicitly compare
54// the suite names. This may not be strictly necessary, but it seems
55// wise to be robust to possible future weirdness in that type, and
56// to be certain that our Hash impl is consistent with the Eq impl.
57impl std::cmp::PartialEq for RustlsCacheKey {
58    fn eq(&self, other: &RustlsCacheKey) -> bool {
59        if self.insecure != other.insecure {
60            return false;
61        }
62        self.rustls_cipher_suites
63            .iter()
64            .map(|s| s.suite())
65            .eq(other.rustls_cipher_suites.iter().map(|s| s.suite()))
66    }
67}
68
69impl std::cmp::Eq for RustlsCacheKey {}
70
71impl std::hash::Hash for RustlsCacheKey {
72    fn hash<H>(&self, hasher: &mut H)
73    where
74        H: std::hash::Hasher,
75    {
76        self.insecure.hash(hasher);
77        for suite in &self.rustls_cipher_suites {
78            suite.suite().as_str().hash(hasher);
79        }
80        if let Some(pem) = &self.certificate_from_pem {
81            pem.as_ref().clone().into_vec().hash(hasher);
82        }
83        if let Some(pem) = &self.private_key_from_pem {
84            pem.as_ref().clone().into_vec().hash(hasher);
85        }
86    }
87}
88
89lruttl::declare_cache! {
90/// Caches TLS connector information for the RFC5321 SMTP client
91static RUSTLS_CACHE: LruCacheWithTtl<RustlsCacheKey, Arc<ClientConfig>>::new("rustls_client_config", 32);
92}
93
94impl RustlsCacheKey {
95    fn get(&self) -> Option<Arc<ClientConfig>> {
96        RUSTLS_CACHE.get(self)
97    }
98
99    async fn set(self, value: Arc<ClientConfig>) {
100        RUSTLS_CACHE
101            .insert(
102                self,
103                value,
104                // We allow the state to be cached for up to 15 minutes at
105                // a time so that we have an opportunity to reload the
106                // system certificates within a reasonable time frame
107                // as/when they are updated by the system.
108                Instant::now() + Duration::from_secs(15 * 60),
109            )
110            .await;
111    }
112}
113
114#[derive(Debug, Clone, Default)]
115pub struct TlsOptions {
116    pub insecure: bool,
117    pub alt_name: Option<String>,
118    pub dane_tlsa: Vec<TLSA>,
119    pub prefer_openssl: bool,
120    pub certificate_from_pem: Option<Arc<Box<[u8]>>>,
121    pub private_key_from_pem: Option<Arc<Box<[u8]>>>,
122    pub openssl_cipher_list: Option<String>,
123    pub openssl_cipher_suites: Option<String>,
124    pub openssl_options: Option<SslOptions>,
125    pub rustls_cipher_suites: Vec<SupportedCipherSuite>,
126}
127
128impl TlsOptions {
129    /// Produce a TlsConnector for this set of TlsOptions.
130    /// We need to employ a cache around the verifier as loading
131    /// the system certificate store can be a non-trivial operation
132    /// and not be something we want to do repeatedly in a hot code
133    /// path.  The cache does unfortunately complicate some of the
134    /// internals here.
135    pub async fn build_tls_connector(&self) -> anyhow::Result<TlsConnector> {
136        let key = RustlsCacheKey {
137            insecure: self.insecure,
138            rustls_cipher_suites: self.rustls_cipher_suites.clone(),
139            certificate_from_pem: self.certificate_from_pem.clone(),
140            private_key_from_pem: self.private_key_from_pem.clone(),
141        };
142        if let Some(config) = key.get() {
143            return Ok(TlsConnector::from(config));
144        }
145        let cipher_suites = if self.rustls_cipher_suites.is_empty() {
146            provider::DEFAULT_CIPHER_SUITES
147        } else {
148            &self.rustls_cipher_suites
149        };
150
151        let provider = Arc::new(CryptoProvider {
152            cipher_suites: cipher_suites.to_vec(),
153            ..provider::default_provider()
154        });
155
156        let verifier: Arc<dyn ServerCertVerifier> = if self.insecure {
157            Arc::new(danger::NoCertificateVerification::new(provider.clone()))
158        } else {
159            Arc::new(rustls_platform_verifier::Verifier::new().with_provider(provider.clone()))
160        };
161
162        let rustls_certificate = self.load_tls_cert().await?;
163        let rustls_private_key = self.load_private_key().await?;
164
165        let builder = ClientConfig::builder_with_provider(provider.clone())
166            .with_protocol_versions(tokio_rustls::rustls::DEFAULT_VERSIONS)
167            .expect("inconsistent cipher-suite/versions selected")
168            .dangerous()
169            .with_custom_certificate_verifier(verifier.clone());
170        let config = match (&rustls_certificate, &rustls_private_key) {
171            (Some(certs), Some(key)) => builder
172                .clone()
173                .with_client_auth_cert(certs.as_ref().clone(), key.as_ref().clone_key()),
174            _ => Ok(builder.with_no_client_auth()),
175        }?;
176
177        let config = Arc::new(config);
178        key.set(config.clone()).await;
179
180        Ok(TlsConnector::from(config))
181    }
182
183    async fn load_tls_cert(&self) -> std::io::Result<Option<Arc<Vec<CertificateDer<'static>>>>> {
184        match &self.certificate_from_pem {
185            Some(pem) => {
186                let data = pem.as_ref().clone().into_vec();
187                let mut reader = BufReader::new(data.as_slice());
188                let certs = certs(&mut reader)
189                    .map(|r| r.map(CertificateDer::into_owned))
190                    .collect::<Result<Vec<CertificateDer<'static>>, std::io::Error>>()?;
191                Ok(Some(Arc::new(certs)))
192            }
193            None => return Ok(None),
194        }
195    }
196
197    async fn load_private_key(&self) -> std::io::Result<Option<Arc<PrivateKeyDer<'static>>>> {
198        match &self.private_key_from_pem {
199            Some(pem) => {
200                let data = pem.as_ref().clone().into_vec();
201
202                // Try to parse as PKCS#8
203                let pkcs8_keys: Vec<PrivateKeyDer<'static>> = {
204                    let mut reader = BufReader::new(data.as_slice());
205                    rustls_pemfile::pkcs8_private_keys(&mut reader)
206                        .map(|r| r.map(PrivateKeyDer::Pkcs8))
207                        .collect::<Result<Vec<PrivateKeyDer<'static>>, std::io::Error>>()?
208                };
209
210                if !pkcs8_keys.is_empty() {
211                    return Ok(pkcs8_keys.into_iter().next().map(Arc::new));
212                }
213
214                // Reset reader and try as RSA PKCS#1
215                let rsa_keys: Vec<PrivateKeyDer<'static>> = {
216                    let mut reader = BufReader::new(data.as_slice());
217                    rustls_pemfile::rsa_private_keys(&mut reader)
218                        .map(|r| r.map(PrivateKeyDer::Pkcs1))
219                        .collect::<Result<Vec<PrivateKeyDer<'static>>, std::io::Error>>()?
220                };
221
222                if !rsa_keys.is_empty() {
223                    return Ok(rsa_keys.into_iter().next().map(Arc::new));
224                }
225
226                // Reset reader and try as EC Sec1
227                let ec_keys: Vec<PrivateKeyDer<'static>> = {
228                    let mut reader = BufReader::new(data.as_slice());
229                    rustls_pemfile::ec_private_keys(&mut reader)
230                        .map(|r| r.map(PrivateKeyDer::Sec1))
231                        .collect::<Result<Vec<PrivateKeyDer<'static>>, std::io::Error>>()?
232                };
233
234                if !ec_keys.is_empty() {
235                    return Ok(ec_keys.into_iter().next().map(Arc::new));
236                }
237
238                Err(std::io::Error::new(
239                    std::io::ErrorKind::InvalidData,
240                    "No private key found in PEM file",
241                ))
242            }
243            None => return Ok(None),
244        }
245    }
246
247    /// Build an OpenSSL connector configuration for use with DANE TLSA or
248    /// when OpenSSL-specific features are needed.
249    pub fn build_openssl_connector(
250        &self,
251        hostname: &str,
252    ) -> Result<openssl::ssl::ConnectConfiguration, OpensslConnectorError> {
253        tracing::trace!("build_openssl_connector for {hostname}");
254        let mut builder =
255            openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls_client())?;
256
257        if let (Some(cert_data), Some(key_data)) =
258            (&self.certificate_from_pem, &self.private_key_from_pem)
259        {
260            let certs = X509::stack_from_pem(cert_data)?;
261            let Some(leaf) = certs.first().cloned() else {
262                return Err(OpensslConnectorError::SslErrorStack(
263                    "certificate PEM data is empty".to_string(),
264                ));
265            };
266            builder.set_certificate(&leaf)?;
267
268            // Add intermediates
269            for cert in certs.iter().skip(1) {
270                builder.add_extra_chain_cert(cert.clone())?;
271            }
272
273            let key = PKey::private_key_from_pem(key_data)?;
274            builder.set_private_key(&key)?;
275
276            builder.check_private_key()?;
277        }
278
279        if let Some(list) = &self.openssl_cipher_list {
280            builder.set_cipher_list(list)?;
281        }
282
283        if let Some(suites) = &self.openssl_cipher_suites {
284            builder.set_ciphersuites(suites)?;
285        }
286
287        if let Some(options) = &self.openssl_options {
288            builder.clear_options(SslOptions::all());
289            builder.set_options(*options);
290        }
291
292        if self.insecure {
293            builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
294        }
295
296        if !self.dane_tlsa.is_empty() {
297            builder.dane_enable()?;
298            builder.set_no_dane_ee_namechecks();
299        }
300
301        let connector = builder.build();
302
303        let mut config = connector.configure()?;
304
305        if !self.dane_tlsa.is_empty() {
306            config.dane_enable(hostname)?;
307            let mut any_usable = false;
308            for tlsa in &self.dane_tlsa {
309                let usable = config.dane_tlsa_add(
310                    match tlsa.cert_usage {
311                        CertUsage::PkixTa => DaneUsage::PKIX_TA,
312                        CertUsage::PkixEe => DaneUsage::PKIX_EE,
313                        CertUsage::DaneTa => DaneUsage::DANE_TA,
314                        CertUsage::DaneEe => DaneUsage::DANE_EE,
315                        CertUsage::Unassigned(n) => DaneUsage::from_raw(n),
316                        CertUsage::Private => DaneUsage::PRIV_CERT,
317                    },
318                    match tlsa.selector {
319                        Selector::Full => DaneSelector::CERT,
320                        Selector::Spki => DaneSelector::SPKI,
321                        Selector::Unassigned(n) => DaneSelector::from_raw(n),
322                        Selector::Private => DaneSelector::PRIV_SEL,
323                    },
324                    match tlsa.matching {
325                        Matching::Raw => DaneMatchType::FULL,
326                        Matching::Sha256 => DaneMatchType::SHA2_256,
327                        Matching::Sha512 => DaneMatchType::SHA2_512,
328                        Matching::Unassigned(n) => DaneMatchType::from_raw(n),
329                        Matching::Private => DaneMatchType::PRIV_MATCH,
330                    },
331                    &tlsa.cert_data,
332                )?;
333
334                tracing::trace!("build_dane_connector usable={usable} {tlsa:?}");
335                if usable {
336                    any_usable = true;
337                }
338            }
339
340            if !any_usable {
341                return Err(OpensslConnectorError::NoUsableDaneTlsa {
342                    hostname: hostname.to_string(),
343                    tlsa: self.dane_tlsa.clone(),
344                });
345            }
346        }
347
348        Ok(config)
349    }
350}
351
352mod danger {
353    use std::sync::Arc;
354    use tokio_rustls::rustls::client::danger::{
355        HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
356    };
357    use tokio_rustls::rustls::crypto::{
358        verify_tls12_signature, verify_tls13_signature, CryptoProvider,
359    };
360    use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
361    use tokio_rustls::rustls::DigitallySignedStruct;
362
363    #[derive(Debug)]
364    pub struct NoCertificateVerification(Arc<CryptoProvider>);
365
366    impl NoCertificateVerification {
367        pub fn new(provider: Arc<CryptoProvider>) -> Self {
368            Self(provider)
369        }
370    }
371
372    impl ServerCertVerifier for NoCertificateVerification {
373        fn verify_server_cert(
374            &self,
375            _end_entity: &CertificateDer<'_>,
376            _intermediates: &[CertificateDer<'_>],
377            _server_name: &ServerName<'_>,
378            _ocsp: &[u8],
379            _now: UnixTime,
380        ) -> Result<ServerCertVerified, tokio_rustls::rustls::Error> {
381            Ok(ServerCertVerified::assertion())
382        }
383
384        fn verify_tls12_signature(
385            &self,
386            message: &[u8],
387            cert: &CertificateDer<'_>,
388            dss: &DigitallySignedStruct,
389        ) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
390            verify_tls12_signature(
391                message,
392                cert,
393                dss,
394                &self.0.signature_verification_algorithms,
395            )
396        }
397
398        fn verify_tls13_signature(
399            &self,
400            message: &[u8],
401            cert: &CertificateDer<'_>,
402            dss: &DigitallySignedStruct,
403        ) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
404            verify_tls13_signature(
405                message,
406                cert,
407                dss,
408                &self.0.signature_verification_algorithms,
409            )
410        }
411
412        fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
413            self.0.signature_verification_algorithms.supported_schemes()
414        }
415    }
416}