lettre/transport/smtp/client/
tls.rs

1use std::fmt::{self, Debug};
2#[cfg(feature = "rustls")]
3use std::sync::Arc;
4
5#[cfg(feature = "boring-tls")]
6use boring::{
7    pkey::PKey,
8    ssl::{SslConnector, SslVersion},
9    x509::store::X509StoreBuilder,
10};
11#[cfg(feature = "native-tls")]
12use native_tls::{Protocol, TlsConnector};
13#[cfg(feature = "rustls")]
14use rustls::{
15    client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
16    crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
17    pki_types::{self, pem::PemObject, CertificateDer, PrivateKeyDer, ServerName, UnixTime},
18    server::ParsedCertificate,
19    ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
20};
21
22#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
23use crate::transport::smtp::{error, Error};
24
25/// TLS protocol versions.
26#[derive(Debug, Copy, Clone)]
27#[non_exhaustive]
28#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
29pub enum TlsVersion {
30    /// TLS 1.0
31    ///
32    /// Should only be used when trying to support legacy
33    /// SMTP servers that haven't updated to
34    /// at least TLS 1.2 yet.
35    ///
36    /// Supported by `native-tls` and `boring-tls`.
37    Tlsv10,
38    /// TLS 1.1
39    ///
40    /// Should only be used when trying to support legacy
41    /// SMTP servers that haven't updated to
42    /// at least TLS 1.2 yet.
43    ///
44    /// Supported by `native-tls` and `boring-tls`.
45    Tlsv11,
46    /// TLS 1.2
47    ///
48    /// A good option for most SMTP servers.
49    ///
50    /// Supported by all TLS backends.
51    Tlsv12,
52    /// TLS 1.3
53    ///
54    /// The most secure option, although not supported by all SMTP servers.
55    ///
56    /// Although it is technically supported by all TLS backends,
57    /// trying to set it for `native-tls` will give a runtime error.
58    Tlsv13,
59}
60
61/// Specifies how to establish a TLS connection
62///
63/// TLDR: Use [`Tls::Wrapper`] or [`Tls::Required`] when
64/// connecting to a remote server, [`Tls::None`] when
65/// connecting to a local server.
66#[derive(Clone)]
67#[allow(missing_copy_implementations)]
68#[cfg_attr(
69    not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
70    deprecated(
71        note = "starting from lettre v0.12 `Tls` won't be available when none of the TLS backends are enabled"
72    )
73)]
74#[cfg_attr(
75    docsrs,
76    doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
77)]
78pub enum Tls {
79    /// Insecure (plaintext) connection only.
80    ///
81    /// This option **always** uses a plaintext connection and should only
82    /// be used for trusted local relays. It is **highly discouraged**
83    /// for remote servers, as it exposes credentials and emails to potential
84    /// interception.
85    ///
86    /// Note: Servers requiring credentials or emails to be sent over TLS
87    /// may reject connections when this option is used.
88    None,
89    /// Begin with a plaintext connection and attempt to use `STARTTLS` if available.
90    ///
91    /// lettre will try to upgrade to a TLS-secured connection but will fall back
92    /// to plaintext if the server does not support TLS. This option is provided for
93    /// compatibility but is **strongly discouraged**, as it exposes connections to
94    /// potential MITM (man-in-the-middle) attacks.
95    ///
96    /// Warning: A malicious intermediary could intercept the `STARTTLS` flag,
97    /// causing lettre to believe the server only supports plaintext connections.
98    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
99    #[cfg_attr(
100        docsrs,
101        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
102    )]
103    Opportunistic(TlsParameters),
104    /// Begin with a plaintext connection and require `STARTTLS` for security.
105    ///
106    /// lettre will upgrade plaintext TCP connections to TLS before transmitting
107    /// any sensitive data. If the server does not support TLS, the connection
108    /// attempt will fail, ensuring no credentials or emails are sent in plaintext.
109    ///
110    /// Unlike [`Tls::Opportunistic`], this option is secure against MITM attacks.
111    /// For optimal security and performance, consider using [`Tls::Wrapper`] instead,
112    /// as it requires fewer roundtrips to establish a secure connection.
113    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
114    #[cfg_attr(
115        docsrs,
116        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
117    )]
118    Required(TlsParameters),
119    /// Establish a connection wrapped in TLS from the start.
120    ///
121    /// lettre connects to the server and immediately performs a TLS handshake.
122    /// If the handshake fails, the connection attempt is aborted without
123    /// transmitting any sensitive data.
124    ///
125    /// This is the fastest and most secure option for establishing a connection.
126    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
127    #[cfg_attr(
128        docsrs,
129        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
130    )]
131    Wrapper(TlsParameters),
132}
133
134impl Debug for Tls {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match &self {
137            Self::None => f.pad("None"),
138            #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
139            Self::Opportunistic(_) => f.pad("Opportunistic"),
140            #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
141            Self::Required(_) => f.pad("Required"),
142            #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
143            Self::Wrapper(_) => f.pad("Wrapper"),
144        }
145    }
146}
147
148/// Source for the base set of root certificates to trust.
149#[allow(missing_copy_implementations)]
150#[derive(Clone, Debug, Default)]
151#[cfg_attr(
152    not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
153    deprecated(
154        note = "starting from lettre v0.12 `CertificateStore` won't be available when none of the TLS backends are enabled"
155    )
156)]
157#[cfg_attr(
158    docsrs,
159    doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
160)]
161pub enum CertificateStore {
162    /// Use the default for the TLS backend.
163    ///
164    /// For native-tls, this will use the system certificate store on Windows, the keychain on
165    /// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`).
166    ///
167    /// For rustls, this will use the system certificate verifier if the `rustls-platform-verifier`
168    /// feature is enabled. If the `rustls-native-certs` feature is enabled, system certificate
169    /// store will be used. Otherwise, it will fall back to `webpki-roots`.
170    ///
171    /// The boring-tls backend uses the same logic as OpenSSL on all platforms.
172    #[default]
173    Default,
174    /// Use a hardcoded set of Mozilla roots via the `webpki-roots` crate.
175    ///
176    /// This option is only available in the rustls backend.
177    #[cfg(all(feature = "rustls", feature = "webpki-roots"))]
178    WebpkiRoots,
179    /// Don't use any system certificates.
180    None,
181}
182
183/// Parameters to use for secure clients
184#[derive(Clone)]
185#[cfg_attr(
186    not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
187    deprecated(
188        note = "starting from lettre v0.12 `TlsParameters` won't be available when none of the TLS backends are enabled"
189    )
190)]
191#[cfg_attr(
192    docsrs,
193    doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
194)]
195pub struct TlsParameters {
196    pub(crate) connector: InnerTlsParameters,
197    /// The domain name which is expected in the TLS certificate from the server
198    pub(super) domain: String,
199}
200
201/// Builder for `TlsParameters`
202#[derive(Debug, Clone)]
203#[cfg_attr(
204    not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
205    deprecated(
206        note = "starting from lettre v0.12 `TlsParametersBuilder` won't be available when none of the TLS backends are enabled"
207    )
208)]
209#[cfg_attr(
210    docsrs,
211    doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
212)]
213pub struct TlsParametersBuilder {
214    domain: String,
215    cert_store: CertificateStore,
216    root_certs: Vec<Certificate>,
217    identity: Option<Identity>,
218    accept_invalid_hostnames: bool,
219    accept_invalid_certs: bool,
220    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
221    min_tls_version: TlsVersion,
222}
223
224impl TlsParametersBuilder {
225    /// Creates a new builder for `TlsParameters`
226    pub fn new(domain: String) -> Self {
227        Self {
228            domain,
229            cert_store: CertificateStore::Default,
230            root_certs: Vec::new(),
231            identity: None,
232            accept_invalid_hostnames: false,
233            accept_invalid_certs: false,
234            #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
235            min_tls_version: TlsVersion::Tlsv12,
236        }
237    }
238
239    /// Set the source for the base set of root certificates to trust.
240    pub fn certificate_store(mut self, cert_store: CertificateStore) -> Self {
241        self.cert_store = cert_store;
242        self
243    }
244
245    /// Add a custom root certificate
246    ///
247    /// Can be used to safely connect to a server using a self-signed certificate, for example.
248    pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
249        self.root_certs.push(cert);
250        self
251    }
252
253    /// Add a client certificate
254    ///
255    /// Can be used to configure a client certificate to present to the server.
256    pub fn identify_with(mut self, identity: Identity) -> Self {
257        self.identity = Some(identity);
258        self
259    }
260
261    /// Controls whether certificates with an invalid hostname are accepted
262    ///
263    /// This option is silently disabled when using `rustls-platform-verifier`.
264    ///
265    /// Defaults to `false`.
266    ///
267    /// # Warning
268    ///
269    /// You should think very carefully before using this method.
270    /// If hostname verification is disabled *any* valid certificate,
271    /// including those from other sites, are trusted.
272    ///
273    /// This method introduces significant vulnerabilities to man-in-the-middle attacks.
274    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
275    #[cfg_attr(
276        docsrs,
277        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
278    )]
279    pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
280        self.accept_invalid_hostnames = accept_invalid_hostnames;
281        self
282    }
283
284    /// Controls which minimum TLS version is allowed
285    ///
286    /// Defaults to [`Tlsv12`][TlsVersion::Tlsv12].
287    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
288    #[cfg_attr(
289        docsrs,
290        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
291    )]
292    pub fn set_min_tls_version(mut self, min_tls_version: TlsVersion) -> Self {
293        self.min_tls_version = min_tls_version;
294        self
295    }
296
297    /// Controls whether invalid certificates are accepted
298    ///
299    /// Defaults to `false`.
300    ///
301    /// # Warning
302    ///
303    /// You should think very carefully before using this method.
304    /// If certificate verification is disabled, *any* certificate
305    /// is trusted for use, including:
306    ///
307    /// * Self signed certificates
308    /// * Certificates from different hostnames
309    /// * Expired certificates
310    ///
311    /// This method should only be used as a last resort, as it introduces
312    /// significant vulnerabilities to man-in-the-middle attacks.
313    pub fn dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
314        self.accept_invalid_certs = accept_invalid_certs;
315        self
316    }
317
318    /// Creates a new `TlsParameters` using native-tls, boring-tls or rustls
319    /// depending on which one is available
320    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
321    #[cfg_attr(
322        docsrs,
323        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
324    )]
325    pub fn build(self) -> Result<TlsParameters, Error> {
326        #[cfg(feature = "rustls")]
327        return self.build_rustls();
328        #[cfg(all(not(feature = "rustls"), feature = "native-tls"))]
329        return self.build_native();
330        #[cfg(all(not(feature = "rustls"), feature = "boring-tls"))]
331        return self.build_boring();
332    }
333
334    /// Creates a new `TlsParameters` using native-tls with the provided configuration
335    #[cfg(feature = "native-tls")]
336    #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
337    pub fn build_native(self) -> Result<TlsParameters, Error> {
338        let mut tls_builder = TlsConnector::builder();
339
340        match self.cert_store {
341            CertificateStore::Default => {}
342            CertificateStore::None => {
343                tls_builder.disable_built_in_roots(true);
344            }
345            #[allow(unreachable_patterns)]
346            other => {
347                return Err(error::tls(format!(
348                    "{other:?} is not supported in native tls"
349                )))
350            }
351        }
352        for cert in self.root_certs {
353            tls_builder.add_root_certificate(cert.native_tls);
354        }
355        tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames);
356        tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs);
357
358        let min_tls_version = match self.min_tls_version {
359            TlsVersion::Tlsv10 => Protocol::Tlsv10,
360            TlsVersion::Tlsv11 => Protocol::Tlsv11,
361            TlsVersion::Tlsv12 => Protocol::Tlsv12,
362            TlsVersion::Tlsv13 => {
363                return Err(error::tls(
364                    "min tls version Tlsv13 not supported in native tls",
365                ))
366            }
367        };
368
369        tls_builder.min_protocol_version(Some(min_tls_version));
370        if let Some(identity) = self.identity {
371            tls_builder.identity(identity.native_tls);
372        }
373
374        let connector = tls_builder.build().map_err(error::tls)?;
375        Ok(TlsParameters {
376            connector: InnerTlsParameters::NativeTls { connector },
377            domain: self.domain,
378        })
379    }
380
381    /// Creates a new `TlsParameters` using boring-tls with the provided configuration
382    #[cfg(feature = "boring-tls")]
383    #[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
384    pub fn build_boring(self) -> Result<TlsParameters, Error> {
385        use boring::ssl::{SslMethod, SslVerifyMode};
386
387        let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
388
389        if self.accept_invalid_certs {
390            tls_builder.set_verify(SslVerifyMode::NONE);
391        } else {
392            match self.cert_store {
393                CertificateStore::Default => {}
394                CertificateStore::None => {
395                    // Replace the default store with an empty store.
396                    tls_builder
397                        .set_cert_store(X509StoreBuilder::new().map_err(error::tls)?.build());
398                }
399                #[allow(unreachable_patterns)]
400                other => {
401                    return Err(error::tls(format!(
402                        "{other:?} is not supported in boring tls"
403                    )))
404                }
405            }
406
407            let cert_store = tls_builder.cert_store_mut();
408
409            for cert in self.root_certs {
410                cert_store.add_cert(cert.boring_tls).map_err(error::tls)?;
411            }
412        }
413
414        if let Some(identity) = self.identity {
415            tls_builder
416                .set_certificate(identity.boring_tls.0.as_ref())
417                .map_err(error::tls)?;
418            tls_builder
419                .set_private_key(identity.boring_tls.1.as_ref())
420                .map_err(error::tls)?;
421        }
422
423        let min_tls_version = match self.min_tls_version {
424            TlsVersion::Tlsv10 => SslVersion::TLS1,
425            TlsVersion::Tlsv11 => SslVersion::TLS1_1,
426            TlsVersion::Tlsv12 => SslVersion::TLS1_2,
427            TlsVersion::Tlsv13 => SslVersion::TLS1_3,
428        };
429
430        tls_builder
431            .set_min_proto_version(Some(min_tls_version))
432            .map_err(error::tls)?;
433        let connector = tls_builder.build();
434        Ok(TlsParameters {
435            connector: InnerTlsParameters::BoringTls {
436                connector,
437                accept_invalid_hostnames: self.accept_invalid_hostnames,
438            },
439            domain: self.domain,
440        })
441    }
442
443    /// Creates a new `TlsParameters` using rustls with the provided configuration
444    #[cfg(feature = "rustls")]
445    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
446    pub fn build_rustls(self) -> Result<TlsParameters, Error> {
447        let just_version3 = &[&rustls::version::TLS13];
448        let supported_versions = match self.min_tls_version {
449            TlsVersion::Tlsv10 => {
450                return Err(error::tls("min tls version Tlsv10 not supported in rustls"))
451            }
452            TlsVersion::Tlsv11 => {
453                return Err(error::tls("min tls version Tlsv11 not supported in rustls"))
454            }
455            TlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
456            TlsVersion::Tlsv13 => just_version3,
457        };
458
459        let crypto_provider = crate::rustls_crypto::crypto_provider();
460        let tls = ClientConfig::builder_with_provider(Arc::clone(&crypto_provider))
461            .with_protocol_versions(supported_versions)
462            .map_err(error::tls)?;
463
464        // Build TLS config
465        let mut root_cert_store = RootCertStore::empty();
466
467        #[cfg(all(
468            not(feature = "rustls-platform-verifier"),
469            feature = "rustls-native-certs"
470        ))]
471        fn load_native_roots(store: &mut RootCertStore) {
472            let rustls_native_certs::CertificateResult { certs, errors, .. } =
473                rustls_native_certs::load_native_certs();
474            let errors_len = errors.len();
475
476            let (added, ignored) = store.add_parsable_certificates(certs);
477            #[cfg(feature = "tracing")]
478            tracing::debug!(
479                "loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
480            );
481            #[cfg(not(feature = "tracing"))]
482            let _ = (errors_len, added, ignored);
483        }
484
485        #[cfg(all(feature = "rustls", feature = "webpki-roots"))]
486        fn load_webpki_roots(store: &mut RootCertStore) {
487            store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
488        }
489
490        #[cfg_attr(not(feature = "rustls-platform-verifier"), allow(unused_mut))]
491        let mut extra_roots = None::<Vec<CertificateDer<'static>>>;
492        match self.cert_store {
493            CertificateStore::Default => {
494                #[cfg(feature = "rustls-platform-verifier")]
495                {
496                    extra_roots = Some(Vec::new());
497                }
498
499                #[cfg(all(
500                    not(feature = "rustls-platform-verifier"),
501                    feature = "rustls-native-certs"
502                ))]
503                load_native_roots(&mut root_cert_store);
504
505                #[cfg(all(
506                    not(feature = "rustls-platform-verifier"),
507                    not(feature = "rustls-native-certs"),
508                    feature = "webpki-roots"
509                ))]
510                load_webpki_roots(&mut root_cert_store);
511            }
512            #[cfg(all(feature = "rustls", feature = "webpki-roots"))]
513            CertificateStore::WebpkiRoots => {
514                load_webpki_roots(&mut root_cert_store);
515            }
516            CertificateStore::None => {}
517        }
518        for cert in self.root_certs {
519            for rustls_cert in cert.rustls {
520                #[cfg(feature = "rustls-platform-verifier")]
521                if let Some(extra_roots) = &mut extra_roots {
522                    extra_roots.push(rustls_cert.clone());
523                }
524                root_cert_store.add(rustls_cert).map_err(error::tls)?;
525            }
526        }
527
528        let tls = if self.accept_invalid_certs
529            || (extra_roots.is_none() && self.accept_invalid_hostnames)
530        {
531            let verifier = InvalidCertsVerifier {
532                ignore_invalid_hostnames: self.accept_invalid_hostnames,
533                ignore_invalid_certs: self.accept_invalid_certs,
534                roots: root_cert_store,
535                crypto_provider,
536            };
537            tls.dangerous()
538                .with_custom_certificate_verifier(Arc::new(verifier))
539        } else {
540            #[cfg(feature = "rustls-platform-verifier")]
541            if let Some(extra_roots) = extra_roots {
542                tls.dangerous().with_custom_certificate_verifier(Arc::new(
543                    rustls_platform_verifier::Verifier::new_with_extra_roots(
544                        extra_roots,
545                        crypto_provider,
546                    )
547                    .map_err(error::tls)?,
548                ))
549            } else {
550                tls.with_root_certificates(root_cert_store)
551            }
552
553            #[cfg(not(feature = "rustls-platform-verifier"))]
554            {
555                tls.with_root_certificates(root_cert_store)
556            }
557        };
558
559        let tls = if let Some(identity) = self.identity {
560            let (client_certificates, private_key) = identity.rustls_tls;
561            tls.with_client_auth_cert(client_certificates, private_key)
562                .map_err(error::tls)?
563        } else {
564            tls.with_no_client_auth()
565        };
566
567        Ok(TlsParameters {
568            connector: InnerTlsParameters::Rustls {
569                config: Arc::new(tls),
570            },
571            domain: self.domain,
572        })
573    }
574}
575
576#[derive(Clone)]
577#[allow(clippy::enum_variant_names)]
578pub(crate) enum InnerTlsParameters {
579    #[cfg(feature = "native-tls")]
580    NativeTls { connector: TlsConnector },
581    #[cfg(feature = "rustls")]
582    Rustls { config: Arc<ClientConfig> },
583    #[cfg(feature = "boring-tls")]
584    BoringTls {
585        connector: SslConnector,
586        accept_invalid_hostnames: bool,
587    },
588}
589
590impl TlsParameters {
591    /// Creates a new `TlsParameters` using native-tls or rustls
592    /// depending on which one is available
593    #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
594    #[cfg_attr(
595        docsrs,
596        doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
597    )]
598    pub fn new(domain: String) -> Result<Self, Error> {
599        TlsParametersBuilder::new(domain).build()
600    }
601
602    /// Creates a new `TlsParameters` builder
603    pub fn builder(domain: String) -> TlsParametersBuilder {
604        TlsParametersBuilder::new(domain)
605    }
606
607    /// Creates a new `TlsParameters` using native-tls
608    #[cfg(feature = "native-tls")]
609    #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
610    pub fn new_native(domain: String) -> Result<Self, Error> {
611        TlsParametersBuilder::new(domain).build_native()
612    }
613
614    /// Creates a new `TlsParameters` using rustls
615    #[cfg(feature = "rustls")]
616    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
617    pub fn new_rustls(domain: String) -> Result<Self, Error> {
618        TlsParametersBuilder::new(domain).build_rustls()
619    }
620
621    /// Creates a new `TlsParameters` using boring
622    #[cfg(feature = "boring-tls")]
623    #[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
624    pub fn new_boring(domain: String) -> Result<Self, Error> {
625        TlsParametersBuilder::new(domain).build_boring()
626    }
627
628    pub fn domain(&self) -> &str {
629        &self.domain
630    }
631}
632
633/// A certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
634#[derive(Clone)]
635#[allow(missing_copy_implementations)]
636#[cfg_attr(
637    not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
638    deprecated(
639        note = "starting from lettre v0.12 `Certificate` won't be available when none of the TLS backends are enabled"
640    )
641)]
642#[cfg_attr(
643    docsrs,
644    doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
645)]
646pub struct Certificate {
647    #[cfg(feature = "native-tls")]
648    native_tls: native_tls::Certificate,
649    #[cfg(feature = "rustls")]
650    rustls: Vec<CertificateDer<'static>>,
651    #[cfg(feature = "boring-tls")]
652    boring_tls: boring::x509::X509,
653}
654
655#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
656impl Certificate {
657    /// Create a `Certificate` from a DER encoded certificate
658    pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
659        #[cfg(feature = "native-tls")]
660        let native_tls_cert = native_tls::Certificate::from_der(&der).map_err(error::tls)?;
661
662        #[cfg(feature = "boring-tls")]
663        let boring_tls_cert = boring::x509::X509::from_der(&der).map_err(error::tls)?;
664
665        Ok(Self {
666            #[cfg(feature = "native-tls")]
667            native_tls: native_tls_cert,
668            #[cfg(feature = "rustls")]
669            rustls: vec![der.into()],
670            #[cfg(feature = "boring-tls")]
671            boring_tls: boring_tls_cert,
672        })
673    }
674
675    /// Create a `Certificate` from a PEM encoded certificate
676    pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
677        #[cfg(feature = "native-tls")]
678        let native_tls_cert = native_tls::Certificate::from_pem(pem).map_err(error::tls)?;
679
680        #[cfg(feature = "boring-tls")]
681        let boring_tls_cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
682
683        #[cfg(feature = "rustls")]
684        let rustls_cert = {
685            CertificateDer::pem_slice_iter(pem)
686                .collect::<Result<Vec<_>, pki_types::pem::Error>>()
687                .map_err(|_| error::tls("invalid certificates"))?
688        };
689
690        Ok(Self {
691            #[cfg(feature = "native-tls")]
692            native_tls: native_tls_cert,
693            #[cfg(feature = "rustls")]
694            rustls: rustls_cert,
695            #[cfg(feature = "boring-tls")]
696            boring_tls: boring_tls_cert,
697        })
698    }
699}
700
701impl Debug for Certificate {
702    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703        f.debug_struct("Certificate").finish()
704    }
705}
706
707/// An identity that can be used with [`TlsParametersBuilder::identify_with`]
708#[allow(missing_copy_implementations)]
709#[cfg_attr(
710    not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
711    deprecated(
712        note = "starting from lettre v0.12 `Identity` won't be available when none of the TLS backends are enabled"
713    )
714)]
715#[cfg_attr(
716    docsrs,
717    doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
718)]
719pub struct Identity {
720    #[cfg(feature = "native-tls")]
721    native_tls: native_tls::Identity,
722    #[cfg(feature = "rustls")]
723    rustls_tls: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
724    #[cfg(feature = "boring-tls")]
725    boring_tls: (boring::x509::X509, PKey<boring::pkey::Private>),
726}
727
728impl Debug for Identity {
729    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730        f.debug_struct("Identity").finish()
731    }
732}
733
734impl Clone for Identity {
735    fn clone(&self) -> Self {
736        Identity {
737            #[cfg(feature = "native-tls")]
738            native_tls: self.native_tls.clone(),
739            #[cfg(feature = "rustls")]
740            rustls_tls: (self.rustls_tls.0.clone(), self.rustls_tls.1.clone_key()),
741            #[cfg(feature = "boring-tls")]
742            boring_tls: (self.boring_tls.0.clone(), self.boring_tls.1.clone()),
743        }
744    }
745}
746
747#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
748impl Identity {
749    pub fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
750        Ok(Self {
751            #[cfg(feature = "native-tls")]
752            native_tls: Identity::from_pem_native_tls(pem, key)?,
753            #[cfg(feature = "rustls")]
754            rustls_tls: Identity::from_pem_rustls_tls(pem, key)?,
755            #[cfg(feature = "boring-tls")]
756            boring_tls: Identity::from_pem_boring_tls(pem, key)?,
757        })
758    }
759
760    #[cfg(feature = "native-tls")]
761    fn from_pem_native_tls(pem: &[u8], key: &[u8]) -> Result<native_tls::Identity, Error> {
762        native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)
763    }
764
765    #[cfg(feature = "rustls")]
766    fn from_pem_rustls_tls(
767        pem: &[u8],
768        key: &[u8],
769    ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
770        let key = match PrivateKeyDer::from_pem_slice(key) {
771            Ok(key) => key,
772            Err(pki_types::pem::Error::NoItemsFound) => {
773                return Err(error::tls("no private key found"))
774            }
775            Err(err) => return Err(error::tls(err)),
776        };
777
778        Ok((vec![pem.to_owned().into()], key))
779    }
780
781    #[cfg(feature = "boring-tls")]
782    fn from_pem_boring_tls(
783        pem: &[u8],
784        key: &[u8],
785    ) -> Result<(boring::x509::X509, PKey<boring::pkey::Private>), Error> {
786        let cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
787        let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
788        Ok((cert, key))
789    }
790}
791
792#[cfg(feature = "rustls")]
793#[derive(Debug)]
794struct InvalidCertsVerifier {
795    ignore_invalid_hostnames: bool,
796    ignore_invalid_certs: bool,
797    roots: RootCertStore,
798    crypto_provider: Arc<CryptoProvider>,
799}
800
801#[cfg(feature = "rustls")]
802impl ServerCertVerifier for InvalidCertsVerifier {
803    fn verify_server_cert(
804        &self,
805        end_entity: &CertificateDer<'_>,
806        intermediates: &[CertificateDer<'_>],
807        server_name: &ServerName<'_>,
808        _ocsp_response: &[u8],
809        now: UnixTime,
810    ) -> Result<ServerCertVerified, TlsError> {
811        let cert = ParsedCertificate::try_from(end_entity)?;
812
813        if !self.ignore_invalid_certs {
814            rustls::client::verify_server_cert_signed_by_trust_anchor(
815                &cert,
816                &self.roots,
817                intermediates,
818                now,
819                self.crypto_provider.signature_verification_algorithms.all,
820            )?;
821        }
822
823        if !self.ignore_invalid_hostnames {
824            rustls::client::verify_server_name(&cert, server_name)?;
825        }
826        Ok(ServerCertVerified::assertion())
827    }
828
829    fn verify_tls12_signature(
830        &self,
831        message: &[u8],
832        cert: &CertificateDer<'_>,
833        dss: &DigitallySignedStruct,
834    ) -> Result<HandshakeSignatureValid, TlsError> {
835        verify_tls12_signature(
836            message,
837            cert,
838            dss,
839            &self.crypto_provider.signature_verification_algorithms,
840        )
841    }
842
843    fn verify_tls13_signature(
844        &self,
845        message: &[u8],
846        cert: &CertificateDer<'_>,
847        dss: &DigitallySignedStruct,
848    ) -> Result<HandshakeSignatureValid, TlsError> {
849        verify_tls13_signature(
850            message,
851            cert,
852            dss,
853            &self.crypto_provider.signature_verification_algorithms,
854        )
855    }
856
857    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
858        self.crypto_provider
859            .signature_verification_algorithms
860            .supported_schemes()
861    }
862}