1 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
2 use crate::transport::smtp::{error, Error};
3 #[cfg(feature = "native-tls")]
4 use native_tls::{Protocol, TlsConnector};
5 #[cfg(feature = "rustls-tls")]
6 use rustls::{
7     client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier},
8     ClientConfig, Error as TlsError, OwnedTrustAnchor, RootCertStore, ServerName,
9 };
10 use std::fmt::{self, Debug};
11 #[cfg(feature = "rustls-tls")]
12 use std::{sync::Arc, time::SystemTime};
13 
14 /// Accepted protocols by default.
15 /// This removes TLS 1.0 and 1.1 compared to tls-native defaults.
16 // This is also rustls' default behavior
17 #[cfg(feature = "native-tls")]
18 const DEFAULT_TLS_MIN_PROTOCOL: Protocol = Protocol::Tlsv12;
19 
20 /// How to apply TLS to a client connection
21 #[derive(Clone)]
22 #[allow(missing_copy_implementations)]
23 pub enum Tls {
24     /// Insecure connection only (for testing purposes)
25     None,
26     /// Start with insecure connection and use `STARTTLS` when available
27     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
28     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
29     Opportunistic(TlsParameters),
30     /// Start with insecure connection and require `STARTTLS`
31     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
32     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
33     Required(TlsParameters),
34     /// Use TLS wrapped connection
35     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
36     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
37     Wrapper(TlsParameters),
38 }
39 
40 impl Debug for Tls {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result41     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42         match &self {
43             Self::None => f.pad("None"),
44             #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
45             Self::Opportunistic(_) => f.pad("Opportunistic"),
46             #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
47             Self::Required(_) => f.pad("Required"),
48             #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
49             Self::Wrapper(_) => f.pad("Wrapper"),
50         }
51     }
52 }
53 
54 /// Parameters to use for secure clients
55 #[derive(Clone)]
56 pub struct TlsParameters {
57     pub(crate) connector: InnerTlsParameters,
58     /// The domain name which is expected in the TLS certificate from the server
59     pub(super) domain: String,
60 }
61 
62 /// Builder for `TlsParameters`
63 #[derive(Debug, Clone)]
64 pub struct TlsParametersBuilder {
65     domain: String,
66     root_certs: Vec<Certificate>,
67     accept_invalid_hostnames: bool,
68     accept_invalid_certs: bool,
69 }
70 
71 impl TlsParametersBuilder {
72     /// Creates a new builder for `TlsParameters`
new(domain: String) -> Self73     pub fn new(domain: String) -> Self {
74         Self {
75             domain,
76             root_certs: Vec::new(),
77             accept_invalid_hostnames: false,
78             accept_invalid_certs: false,
79         }
80     }
81 
82     /// Add a custom root certificate
83     ///
84     /// Can be used to safely connect to a server using a self signed certificate, for example.
add_root_certificate(mut self, cert: Certificate) -> Self85     pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
86         self.root_certs.push(cert);
87         self
88     }
89 
90     /// Controls whether certificates with an invalid hostname are accepted
91     ///
92     /// Defaults to `false`.
93     ///
94     /// # Warning
95     ///
96     /// You should think very carefully before using this method.
97     /// If hostname verification is disabled *any* valid certificate,
98     /// including those from other sites, are trusted.
99     ///
100     /// This method introduces significant vulnerabilities to man-in-the-middle attacks.
101     ///
102     /// Hostname verification can only be disabled with the `native-tls` TLS backend.
103     #[cfg(feature = "native-tls")]
104     #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self105     pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
106         self.accept_invalid_hostnames = accept_invalid_hostnames;
107         self
108     }
109 
110     /// Controls whether invalid certificates are accepted
111     ///
112     /// Defaults to `false`.
113     ///
114     /// # Warning
115     ///
116     /// You should think very carefully before using this method.
117     /// If certificate verification is disabled, *any* certificate
118     /// is trusted for use, including:
119     ///
120     /// * Self signed certificates
121     /// * Certificates from different hostnames
122     /// * Expired certificates
123     ///
124     /// This method should only be used as a last resort, as it introduces
125     /// significant vulnerabilities to man-in-the-middle attacks.
dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self126     pub fn dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
127         self.accept_invalid_certs = accept_invalid_certs;
128         self
129     }
130 
131     /// Creates a new `TlsParameters` using native-tls or rustls
132     /// depending on which one is available
133     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
134     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
build(self) -> Result<TlsParameters, Error>135     pub fn build(self) -> Result<TlsParameters, Error> {
136         #[cfg(feature = "rustls-tls")]
137         return self.build_rustls();
138 
139         #[cfg(not(feature = "rustls-tls"))]
140         return self.build_native();
141     }
142 
143     /// Creates a new `TlsParameters` using native-tls with the provided configuration
144     #[cfg(feature = "native-tls")]
145     #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
build_native(self) -> Result<TlsParameters, Error>146     pub fn build_native(self) -> Result<TlsParameters, Error> {
147         let mut tls_builder = TlsConnector::builder();
148 
149         for cert in self.root_certs {
150             tls_builder.add_root_certificate(cert.native_tls);
151         }
152         tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames);
153         tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs);
154 
155         tls_builder.min_protocol_version(Some(DEFAULT_TLS_MIN_PROTOCOL));
156         let connector = tls_builder.build().map_err(error::tls)?;
157         Ok(TlsParameters {
158             connector: InnerTlsParameters::NativeTls(connector),
159             domain: self.domain,
160         })
161     }
162 
163     /// Creates a new `TlsParameters` using rustls with the provided configuration
164     #[cfg(feature = "rustls-tls")]
165     #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
build_rustls(self) -> Result<TlsParameters, Error>166     pub fn build_rustls(self) -> Result<TlsParameters, Error> {
167         let tls = ClientConfig::builder();
168         let tls = tls.with_safe_defaults();
169 
170         let tls = if self.accept_invalid_certs {
171             tls.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
172         } else {
173             let mut root_cert_store = RootCertStore::empty();
174             for cert in self.root_certs {
175                 for rustls_cert in cert.rustls {
176                     root_cert_store.add(&rustls_cert).map_err(error::tls)?;
177                 }
178             }
179             root_cert_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(
180                 |ta| {
181                     OwnedTrustAnchor::from_subject_spki_name_constraints(
182                         ta.subject,
183                         ta.spki,
184                         ta.name_constraints,
185                     )
186                 },
187             ));
188 
189             tls.with_custom_certificate_verifier(Arc::new(WebPkiVerifier::new(
190                 root_cert_store,
191                 None,
192             )))
193         };
194         let tls = tls.with_no_client_auth();
195 
196         Ok(TlsParameters {
197             connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
198             domain: self.domain,
199         })
200     }
201 }
202 
203 #[derive(Clone)]
204 pub enum InnerTlsParameters {
205     #[cfg(feature = "native-tls")]
206     NativeTls(TlsConnector),
207     #[cfg(feature = "rustls-tls")]
208     RustlsTls(Arc<ClientConfig>),
209 }
210 
211 impl TlsParameters {
212     /// Creates a new `TlsParameters` using native-tls or rustls
213     /// depending on which one is available
214     #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
215     #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
new(domain: String) -> Result<Self, Error>216     pub fn new(domain: String) -> Result<Self, Error> {
217         TlsParametersBuilder::new(domain).build()
218     }
219 
builder(domain: String) -> TlsParametersBuilder220     pub fn builder(domain: String) -> TlsParametersBuilder {
221         TlsParametersBuilder::new(domain)
222     }
223 
224     /// Creates a new `TlsParameters` using native-tls
225     #[cfg(feature = "native-tls")]
226     #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
new_native(domain: String) -> Result<Self, Error>227     pub fn new_native(domain: String) -> Result<Self, Error> {
228         TlsParametersBuilder::new(domain).build_native()
229     }
230 
231     /// Creates a new `TlsParameters` using rustls
232     #[cfg(feature = "rustls-tls")]
233     #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
new_rustls(domain: String) -> Result<Self, Error>234     pub fn new_rustls(domain: String) -> Result<Self, Error> {
235         TlsParametersBuilder::new(domain).build_rustls()
236     }
237 
domain(&self) -> &str238     pub fn domain(&self) -> &str {
239         &self.domain
240     }
241 }
242 
243 /// A client certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
244 #[derive(Clone)]
245 #[allow(missing_copy_implementations)]
246 pub struct Certificate {
247     #[cfg(feature = "native-tls")]
248     native_tls: native_tls::Certificate,
249     #[cfg(feature = "rustls-tls")]
250     rustls: Vec<rustls::Certificate>,
251 }
252 
253 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
254 impl Certificate {
255     /// Create a `Certificate` from a DER encoded certificate
from_der(der: Vec<u8>) -> Result<Self, Error>256     pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
257         #[cfg(feature = "native-tls")]
258         let native_tls_cert = native_tls::Certificate::from_der(&der).map_err(error::tls)?;
259 
260         Ok(Self {
261             #[cfg(feature = "native-tls")]
262             native_tls: native_tls_cert,
263             #[cfg(feature = "rustls-tls")]
264             rustls: vec![rustls::Certificate(der)],
265         })
266     }
267 
268     /// Create a `Certificate` from a PEM encoded certificate
from_pem(pem: &[u8]) -> Result<Self, Error>269     pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
270         #[cfg(feature = "native-tls")]
271         let native_tls_cert = native_tls::Certificate::from_pem(pem).map_err(error::tls)?;
272 
273         #[cfg(feature = "rustls-tls")]
274         let rustls_cert = {
275             use std::io::Cursor;
276 
277             let mut pem = Cursor::new(pem);
278             rustls_pemfile::certs(&mut pem)
279                 .map_err(|_| error::tls("invalid certificates"))?
280                 .into_iter()
281                 .map(rustls::Certificate)
282                 .collect::<Vec<_>>()
283         };
284 
285         Ok(Self {
286             #[cfg(feature = "native-tls")]
287             native_tls: native_tls_cert,
288             #[cfg(feature = "rustls-tls")]
289             rustls: rustls_cert,
290         })
291     }
292 }
293 
294 impl Debug for Certificate {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result295     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296         f.debug_struct("Certificate").finish()
297     }
298 }
299 
300 #[cfg(feature = "rustls-tls")]
301 struct InvalidCertsVerifier;
302 
303 #[cfg(feature = "rustls-tls")]
304 impl ServerCertVerifier for InvalidCertsVerifier {
verify_server_cert( &self, _end_entity: &rustls::Certificate, _intermediates: &[rustls::Certificate], _server_name: &ServerName, _scts: &mut dyn Iterator<Item = &[u8]>, _ocsp_response: &[u8], _now: SystemTime, ) -> Result<ServerCertVerified, TlsError>305     fn verify_server_cert(
306         &self,
307         _end_entity: &rustls::Certificate,
308         _intermediates: &[rustls::Certificate],
309         _server_name: &ServerName,
310         _scts: &mut dyn Iterator<Item = &[u8]>,
311         _ocsp_response: &[u8],
312         _now: SystemTime,
313     ) -> Result<ServerCertVerified, TlsError> {
314         Ok(ServerCertVerified::assertion())
315     }
316 }
317