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                    .into_iter()
190                    .map(|r| r.map(CertificateDer::into_owned))
191                    .collect::<Result<Vec<CertificateDer<'static>>, std::io::Error>>()?;
192                Ok(Some(Arc::new(certs)))
193            }
194            None => return Ok(None),
195        }
196    }
197
198    async fn load_private_key(&self) -> std::io::Result<Option<Arc<PrivateKeyDer<'static>>>> {
199        match &self.private_key_from_pem {
200            Some(pem) => {
201                let data = pem.as_ref().clone().into_vec();
202
203                // Try to parse as PKCS#8
204                let pkcs8_keys: Vec<PrivateKeyDer<'static>> = {
205                    let mut reader = BufReader::new(data.as_slice());
206                    rustls_pemfile::pkcs8_private_keys(&mut reader)
207                        .into_iter()
208                        .map(|r| r.map(PrivateKeyDer::Pkcs8))
209                        .collect::<Result<Vec<PrivateKeyDer<'static>>, std::io::Error>>()?
210                };
211
212                if !pkcs8_keys.is_empty() {
213                    return Ok(pkcs8_keys.into_iter().next().map(Arc::new));
214                }
215
216                // Reset reader and try as RSA PKCS#1
217                let rsa_keys: Vec<PrivateKeyDer<'static>> = {
218                    let mut reader = BufReader::new(data.as_slice());
219                    rustls_pemfile::rsa_private_keys(&mut reader)
220                        .into_iter()
221                        .map(|r| r.map(PrivateKeyDer::Pkcs1))
222                        .collect::<Result<Vec<PrivateKeyDer<'static>>, std::io::Error>>()?
223                };
224
225                if !rsa_keys.is_empty() {
226                    return Ok(rsa_keys.into_iter().next().map(Arc::new));
227                }
228
229                // Reset reader and try as EC Sec1
230                let ec_keys: Vec<PrivateKeyDer<'static>> = {
231                    let mut reader = BufReader::new(data.as_slice());
232                    rustls_pemfile::ec_private_keys(&mut reader)
233                        .into_iter()
234                        .map(|r| r.map(PrivateKeyDer::Sec1))
235                        .collect::<Result<Vec<PrivateKeyDer<'static>>, std::io::Error>>()?
236                };
237
238                if !ec_keys.is_empty() {
239                    return Ok(ec_keys.into_iter().next().map(Arc::new));
240                }
241
242                Err(std::io::Error::new(
243                    std::io::ErrorKind::InvalidData,
244                    "No private key found in PEM file",
245                ))
246            }
247            None => return Ok(None),
248        }
249    }
250
251    /// Build an OpenSSL connector configuration for use with DANE TLSA or
252    /// when OpenSSL-specific features are needed.
253    pub fn build_openssl_connector(
254        &self,
255        hostname: &str,
256    ) -> Result<openssl::ssl::ConnectConfiguration, OpensslConnectorError> {
257        tracing::trace!("build_openssl_connector for {hostname}");
258        let mut builder =
259            openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls_client())?;
260
261        if let (Some(cert_data), Some(key_data)) =
262            (&self.certificate_from_pem, &self.private_key_from_pem)
263        {
264            let cert = X509::from_pem(cert_data)?;
265            builder.set_certificate(&cert)?;
266
267            let key = PKey::private_key_from_pem(key_data)?;
268            builder.set_private_key(&key)?;
269
270            builder.check_private_key()?;
271        }
272
273        if let Some(list) = &self.openssl_cipher_list {
274            builder.set_cipher_list(list)?;
275        }
276
277        if let Some(suites) = &self.openssl_cipher_suites {
278            builder.set_ciphersuites(suites)?;
279        }
280
281        if let Some(options) = &self.openssl_options {
282            builder.clear_options(SslOptions::all());
283            builder.set_options(*options);
284        }
285
286        if self.insecure {
287            builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
288        }
289
290        if !self.dane_tlsa.is_empty() {
291            builder.dane_enable()?;
292            builder.set_no_dane_ee_namechecks();
293        }
294
295        let connector = builder.build();
296
297        let mut config = connector.configure()?;
298
299        if !self.dane_tlsa.is_empty() {
300            config.dane_enable(hostname)?;
301            let mut any_usable = false;
302            for tlsa in &self.dane_tlsa {
303                let usable = config.dane_tlsa_add(
304                    match tlsa.cert_usage() {
305                        CertUsage::PkixTa => DaneUsage::PKIX_TA,
306                        CertUsage::PkixEe => DaneUsage::PKIX_EE,
307                        CertUsage::DaneTa => DaneUsage::DANE_TA,
308                        CertUsage::DaneEe => DaneUsage::DANE_EE,
309                        CertUsage::Unassigned(n) => DaneUsage::from_raw(n),
310                        CertUsage::Private => DaneUsage::PRIV_CERT,
311                    },
312                    match tlsa.selector() {
313                        Selector::Full => DaneSelector::CERT,
314                        Selector::Spki => DaneSelector::SPKI,
315                        Selector::Unassigned(n) => DaneSelector::from_raw(n),
316                        Selector::Private => DaneSelector::PRIV_SEL,
317                    },
318                    match tlsa.matching() {
319                        Matching::Raw => DaneMatchType::FULL,
320                        Matching::Sha256 => DaneMatchType::SHA2_256,
321                        Matching::Sha512 => DaneMatchType::SHA2_512,
322                        Matching::Unassigned(n) => DaneMatchType::from_raw(n),
323                        Matching::Private => DaneMatchType::PRIV_MATCH,
324                    },
325                    tlsa.cert_data(),
326                )?;
327
328                tracing::trace!("build_dane_connector usable={usable} {tlsa:?}");
329                if usable {
330                    any_usable = true;
331                }
332            }
333
334            if !any_usable {
335                return Err(OpensslConnectorError::NoUsableDaneTlsa {
336                    hostname: hostname.to_string(),
337                    tlsa: self.dane_tlsa.clone(),
338                });
339            }
340        }
341
342        Ok(config)
343    }
344}
345
346mod danger {
347    use std::sync::Arc;
348    use tokio_rustls::rustls::client::danger::{
349        HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
350    };
351    use tokio_rustls::rustls::crypto::{
352        verify_tls12_signature, verify_tls13_signature, CryptoProvider,
353    };
354    use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
355    use tokio_rustls::rustls::DigitallySignedStruct;
356
357    #[derive(Debug)]
358    pub struct NoCertificateVerification(Arc<CryptoProvider>);
359
360    impl NoCertificateVerification {
361        pub fn new(provider: Arc<CryptoProvider>) -> Self {
362            Self(provider)
363        }
364    }
365
366    impl ServerCertVerifier for NoCertificateVerification {
367        fn verify_server_cert(
368            &self,
369            _end_entity: &CertificateDer<'_>,
370            _intermediates: &[CertificateDer<'_>],
371            _server_name: &ServerName<'_>,
372            _ocsp: &[u8],
373            _now: UnixTime,
374        ) -> Result<ServerCertVerified, tokio_rustls::rustls::Error> {
375            Ok(ServerCertVerified::assertion())
376        }
377
378        fn verify_tls12_signature(
379            &self,
380            message: &[u8],
381            cert: &CertificateDer<'_>,
382            dss: &DigitallySignedStruct,
383        ) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
384            verify_tls12_signature(
385                message,
386                cert,
387                dss,
388                &self.0.signature_verification_algorithms,
389            )
390        }
391
392        fn verify_tls13_signature(
393            &self,
394            message: &[u8],
395            cert: &CertificateDer<'_>,
396            dss: &DigitallySignedStruct,
397        ) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
398            verify_tls13_signature(
399                message,
400                cert,
401                dss,
402                &self.0.signature_verification_algorithms,
403            )
404        }
405
406        fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
407            self.0.signature_verification_algorithms.supported_schemes()
408        }
409    }
410}