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