Compare commits
26 Commits
semver-com
...
better-tls
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
716d7baac2 | ||
|
|
659b0b50b1 | ||
|
|
f332439000 | ||
|
|
335cdea3f9 | ||
|
|
7642b2130e | ||
|
|
5a3f189e50 | ||
|
|
31b8f297ec | ||
|
|
3a6ab6f398 | ||
|
|
7b8fc5a678 | ||
|
|
2b36935b1f | ||
|
|
d31490a2a9 | ||
|
|
b7482f0232 | ||
|
|
abc8cdf789 | ||
|
|
81b233def4 | ||
|
|
e644a6c2d3 | ||
|
|
0385ca3b19 | ||
|
|
d114f9caf3 | ||
|
|
785307b091 | ||
|
|
f16cbeec51 | ||
|
|
5cbe9ba283 | ||
|
|
2f4e36ac61 | ||
|
|
610b72e93b | ||
|
|
69b7c5500a | ||
|
|
63c5fcccfc | ||
|
|
b583aff36c | ||
|
|
512c5e3ce8 |
@@ -14,8 +14,6 @@ use futures_io::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "async-std1-rustls")]
|
#[cfg(feature = "async-std1-rustls")]
|
||||||
use futures_rustls::client::TlsStream as AsyncStd1RustlsStream;
|
use futures_rustls::client::TlsStream as AsyncStd1RustlsStream;
|
||||||
#[cfg(any(feature = "tokio1-rustls", feature = "async-std1-rustls"))]
|
|
||||||
use rustls::pki_types::ServerName;
|
|
||||||
#[cfg(feature = "tokio1-boring-tls")]
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
use tokio1_boring::SslStream as Tokio1SslStream;
|
use tokio1_boring::SslStream as Tokio1SslStream;
|
||||||
#[cfg(feature = "tokio1")]
|
#[cfg(feature = "tokio1")]
|
||||||
@@ -319,11 +317,9 @@ impl AsyncNetworkStream {
|
|||||||
tcp_stream: Box<dyn AsyncTokioStream>,
|
tcp_stream: Box<dyn AsyncTokioStream>,
|
||||||
tls_parameters: TlsParameters,
|
tls_parameters: TlsParameters,
|
||||||
) -> Result<InnerAsyncNetworkStream, Error> {
|
) -> Result<InnerAsyncNetworkStream, Error> {
|
||||||
let domain = tls_parameters.domain().to_owned();
|
match tls_parameters.inner {
|
||||||
|
|
||||||
match tls_parameters.connector {
|
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
InnerTlsParameters::NativeTls { connector } => {
|
InnerTlsParameters::NativeTls(inner) => {
|
||||||
#[cfg(not(feature = "tokio1-native-tls"))]
|
#[cfg(not(feature = "tokio1-native-tls"))]
|
||||||
panic!("built without the tokio1-native-tls feature");
|
panic!("built without the tokio1-native-tls feature");
|
||||||
|
|
||||||
@@ -331,16 +327,16 @@ impl AsyncNetworkStream {
|
|||||||
return {
|
return {
|
||||||
use tokio1_native_tls_crate::TlsConnector;
|
use tokio1_native_tls_crate::TlsConnector;
|
||||||
|
|
||||||
let connector = TlsConnector::from(connector);
|
let connector = TlsConnector::from(inner.connector);
|
||||||
let stream = connector
|
let stream = connector
|
||||||
.connect(&domain, tcp_stream)
|
.connect(&inner.server_name, tcp_stream)
|
||||||
.await
|
.await
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
Ok(InnerAsyncNetworkStream::Tokio1NativeTls(stream))
|
Ok(InnerAsyncNetworkStream::Tokio1NativeTls(stream))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
InnerTlsParameters::Rustls { config } => {
|
InnerTlsParameters::Rustls(inner) => {
|
||||||
#[cfg(not(feature = "tokio1-rustls"))]
|
#[cfg(not(feature = "tokio1-rustls"))]
|
||||||
panic!("built without the tokio1-rustls feature");
|
panic!("built without the tokio1-rustls feature");
|
||||||
|
|
||||||
@@ -348,31 +344,25 @@ impl AsyncNetworkStream {
|
|||||||
return {
|
return {
|
||||||
use tokio1_rustls::TlsConnector;
|
use tokio1_rustls::TlsConnector;
|
||||||
|
|
||||||
let domain = ServerName::try_from(domain.as_str())
|
let connector = TlsConnector::from(inner.connector);
|
||||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
|
||||||
|
|
||||||
let connector = TlsConnector::from(config);
|
|
||||||
let stream = connector
|
let stream = connector
|
||||||
.connect(domain.to_owned(), tcp_stream)
|
.connect(inner.server_name.inner(), tcp_stream)
|
||||||
.await
|
.await
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
Ok(InnerAsyncNetworkStream::Tokio1Rustls(stream))
|
Ok(InnerAsyncNetworkStream::Tokio1Rustls(stream))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
InnerTlsParameters::BoringTls {
|
InnerTlsParameters::BoringTls(inner) => {
|
||||||
connector,
|
|
||||||
accept_invalid_hostnames,
|
|
||||||
} => {
|
|
||||||
#[cfg(not(feature = "tokio1-boring-tls"))]
|
#[cfg(not(feature = "tokio1-boring-tls"))]
|
||||||
panic!("built without the tokio1-boring-tls feature");
|
panic!("built without the tokio1-boring-tls feature");
|
||||||
|
|
||||||
#[cfg(feature = "tokio1-boring-tls")]
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
return {
|
return {
|
||||||
let mut config = connector.configure().map_err(error::connection)?;
|
let mut config = inner.connector.configure().map_err(error::connection)?;
|
||||||
config.set_verify_hostname(accept_invalid_hostnames);
|
config.set_verify_hostname(inner.extra_info.accept_invalid_hostnames);
|
||||||
|
|
||||||
let stream = tokio1_boring::connect(config, &domain, tcp_stream)
|
let stream = tokio1_boring::connect(config, &inner.server_name, tcp_stream)
|
||||||
.await
|
.await
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
Ok(InnerAsyncNetworkStream::Tokio1BoringTls(stream))
|
Ok(InnerAsyncNetworkStream::Tokio1BoringTls(stream))
|
||||||
@@ -385,17 +375,15 @@ impl AsyncNetworkStream {
|
|||||||
#[cfg(feature = "async-std1-rustls")]
|
#[cfg(feature = "async-std1-rustls")]
|
||||||
async fn upgrade_asyncstd1_tls(
|
async fn upgrade_asyncstd1_tls(
|
||||||
tcp_stream: AsyncStd1TcpStream,
|
tcp_stream: AsyncStd1TcpStream,
|
||||||
mut tls_parameters: TlsParameters,
|
tls_parameters: TlsParameters,
|
||||||
) -> Result<InnerAsyncNetworkStream, Error> {
|
) -> Result<InnerAsyncNetworkStream, Error> {
|
||||||
let domain = mem::take(&mut tls_parameters.domain);
|
match tls_parameters.inner {
|
||||||
|
|
||||||
match tls_parameters.connector {
|
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
InnerTlsParameters::NativeTls { connector } => {
|
InnerTlsParameters::NativeTls(_) => {
|
||||||
panic!("native-tls isn't supported with async-std yet. See https://github.com/lettre/lettre/pull/531#issuecomment-757893531");
|
panic!("native-tls isn't supported with async-std yet. See https://github.com/lettre/lettre/pull/531#issuecomment-757893531");
|
||||||
}
|
}
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
InnerTlsParameters::Rustls { config } => {
|
InnerTlsParameters::Rustls(inner) => {
|
||||||
#[cfg(not(feature = "async-std1-rustls"))]
|
#[cfg(not(feature = "async-std1-rustls"))]
|
||||||
panic!("built without the async-std1-rustls feature");
|
panic!("built without the async-std1-rustls feature");
|
||||||
|
|
||||||
@@ -403,19 +391,16 @@ impl AsyncNetworkStream {
|
|||||||
return {
|
return {
|
||||||
use futures_rustls::TlsConnector;
|
use futures_rustls::TlsConnector;
|
||||||
|
|
||||||
let domain = ServerName::try_from(domain.as_str())
|
let connector = TlsConnector::from(inner.connector);
|
||||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
|
||||||
|
|
||||||
let connector = TlsConnector::from(config);
|
|
||||||
let stream = connector
|
let stream = connector
|
||||||
.connect(domain.to_owned(), tcp_stream)
|
.connect(inner.server_name.inner(), tcp_stream)
|
||||||
.await
|
.await
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
Ok(InnerAsyncNetworkStream::AsyncStd1Rustls(stream))
|
Ok(InnerAsyncNetworkStream::AsyncStd1Rustls(stream))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
InnerTlsParameters::BoringTls { .. } => {
|
InnerTlsParameters::BoringTls(_inner) => {
|
||||||
panic!("boring-tls isn't supported with async-std yet.");
|
panic!("boring-tls isn't supported with async-std yet.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ pub use self::async_net::AsyncNetworkStream;
|
|||||||
pub use self::async_net::AsyncTokioStream;
|
pub use self::async_net::AsyncTokioStream;
|
||||||
use self::net::NetworkStream;
|
use self::net::NetworkStream;
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||||
pub(super) use self::tls::InnerTlsParameters;
|
pub(super) use self::tls::current::InnerTlsParameters;
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||||
pub use self::tls::TlsVersion;
|
pub use self::tls::current::TlsVersion;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
connection::SmtpConnection,
|
connection::SmtpConnection,
|
||||||
tls::{Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder},
|
tls::current::{
|
||||||
|
Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use boring::ssl::SslStream;
|
|||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
use native_tls::TlsStream;
|
use native_tls::TlsStream;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use rustls::{pki_types::ServerName, ClientConnection, StreamOwned};
|
use rustls::{ClientConnection, StreamOwned};
|
||||||
use socket2::{Domain, Protocol, Type};
|
use socket2::{Domain, Protocol, Type};
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||||
@@ -172,33 +172,33 @@ impl NetworkStream {
|
|||||||
tcp_stream: TcpStream,
|
tcp_stream: TcpStream,
|
||||||
tls_parameters: &TlsParameters,
|
tls_parameters: &TlsParameters,
|
||||||
) -> Result<InnerNetworkStream, Error> {
|
) -> Result<InnerNetworkStream, Error> {
|
||||||
Ok(match &tls_parameters.connector {
|
Ok(match &tls_parameters.inner {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
InnerTlsParameters::NativeTls { connector } => {
|
InnerTlsParameters::NativeTls(inner) => {
|
||||||
let stream = connector
|
let stream = inner
|
||||||
.connect(tls_parameters.domain(), tcp_stream)
|
.connector
|
||||||
|
.connect(&inner.server_name, tcp_stream)
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
InnerNetworkStream::NativeTls(stream)
|
InnerNetworkStream::NativeTls(stream)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
InnerTlsParameters::Rustls { config } => {
|
InnerTlsParameters::Rustls(inner) => {
|
||||||
let domain = ServerName::try_from(tls_parameters.domain())
|
let connection = ClientConnection::new(
|
||||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
Arc::clone(&inner.connector),
|
||||||
let connection = ClientConnection::new(Arc::clone(config), domain.to_owned())
|
inner.server_name.inner_ref().clone(),
|
||||||
|
)
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
let stream = StreamOwned::new(connection, tcp_stream);
|
let stream = StreamOwned::new(connection, tcp_stream);
|
||||||
InnerNetworkStream::Rustls(stream)
|
InnerNetworkStream::Rustls(stream)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
InnerTlsParameters::BoringTls {
|
InnerTlsParameters::BoringTls(inner) => {
|
||||||
connector,
|
let stream = inner
|
||||||
accept_invalid_hostnames,
|
.connector
|
||||||
} => {
|
|
||||||
let stream = connector
|
|
||||||
.configure()
|
.configure()
|
||||||
.map_err(error::connection)?
|
.map_err(error::connection)?
|
||||||
.verify_hostname(*accept_invalid_hostnames)
|
.verify_hostname(inner.extra_info.accept_invalid_hostnames)
|
||||||
.connect(tls_parameters.domain(), tcp_stream)
|
.connect(&inner.server_name, tcp_stream)
|
||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
InnerNetworkStream::BoringTls(stream)
|
InnerNetworkStream::BoringTls(stream)
|
||||||
}
|
}
|
||||||
|
|||||||
111
src/transport/smtp/client/tls/boring_tls.rs
Normal file
111
src/transport/smtp/client/tls/boring_tls.rs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
use boring::{
|
||||||
|
ssl::{SslConnector, SslMethod, SslVerifyMode, SslVersion},
|
||||||
|
x509::store::X509StoreBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::transport::smtp::error::{self, Error};
|
||||||
|
|
||||||
|
pub(super) fn build_connector(
|
||||||
|
builder: super::TlsParametersBuilder<super::BoringTls>,
|
||||||
|
) -> Result<(Box<str>, SslConnector), Error> {
|
||||||
|
let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
|
||||||
|
|
||||||
|
if builder.accept_invalid_certs {
|
||||||
|
tls_builder.set_verify(SslVerifyMode::NONE);
|
||||||
|
} else {
|
||||||
|
match builder.cert_store {
|
||||||
|
CertificateStore::System => {}
|
||||||
|
CertificateStore::None => {
|
||||||
|
// Replace the default store with an empty store.
|
||||||
|
tls_builder.set_cert_store(X509StoreBuilder::new().map_err(error::tls)?.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cert_store = tls_builder.cert_store_mut();
|
||||||
|
|
||||||
|
for cert in builder.root_certs {
|
||||||
|
cert_store.add_cert(cert.0).map_err(error::tls)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(identity) = builder.identity {
|
||||||
|
tls_builder
|
||||||
|
.set_certificate(identity.chain.as_ref())
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
tls_builder
|
||||||
|
.set_private_key(identity.key.as_ref())
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_tls_version = match builder.min_tls_version {
|
||||||
|
MinTlsVersion::Tlsv10 => SslVersion::TLS1,
|
||||||
|
MinTlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
||||||
|
MinTlsVersion::Tlsv12 => SslVersion::TLS1_2,
|
||||||
|
MinTlsVersion::Tlsv13 => SslVersion::TLS1_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
tls_builder
|
||||||
|
.set_min_proto_version(Some(min_tls_version))
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
Ok((builder.server_name.into_boxed_str(), tls_builder.build()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[allow(missing_copy_implementations)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(super) enum CertificateStore {
|
||||||
|
#[default]
|
||||||
|
System,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct Certificate(pub(super) boring::x509::X509);
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
pub(super) fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self(boring::x509::X509::from_pem(pem).map_err(error::tls)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_der(der: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self(boring::x509::X509::from_der(der).map_err(error::tls)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Certificate {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Certificate").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct Identity {
|
||||||
|
pub(super) chain: boring::x509::X509,
|
||||||
|
pub(super) key: boring::pkey::PKey<boring::pkey::Private>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Identity {
|
||||||
|
pub(super) fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||||
|
let chain = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||||
|
let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
|
||||||
|
Ok(Self { chain, key })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Identity {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Identity").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(super) enum MinTlsVersion {
|
||||||
|
Tlsv10,
|
||||||
|
Tlsv11,
|
||||||
|
#[default]
|
||||||
|
Tlsv12,
|
||||||
|
Tlsv13,
|
||||||
|
}
|
||||||
@@ -1,26 +1,9 @@
|
|||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[cfg(feature = "boring-tls")]
|
|
||||||
use boring::{
|
|
||||||
pkey::PKey,
|
|
||||||
ssl::{SslConnector, SslVersion},
|
|
||||||
x509::store::X509StoreBuilder,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "native-tls")]
|
|
||||||
use native_tls::{Protocol, TlsConnector};
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
use rustls::{
|
|
||||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
|
||||||
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
|
|
||||||
pki_types::{self, pem::PemObject, CertificateDer, PrivateKeyDer, ServerName, UnixTime},
|
|
||||||
server::ParsedCertificate,
|
|
||||||
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
use super::TlsBackend;
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||||
use crate::transport::smtp::{error, Error};
|
use crate::transport::smtp::error;
|
||||||
|
use crate::transport::smtp::Error;
|
||||||
|
|
||||||
/// TLS protocol versions.
|
/// TLS protocol versions.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -193,9 +176,7 @@ pub enum CertificateStore {
|
|||||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||||
)]
|
)]
|
||||||
pub struct TlsParameters {
|
pub struct TlsParameters {
|
||||||
pub(crate) connector: InnerTlsParameters,
|
pub(in crate::transport::smtp) inner: InnerTlsParameters,
|
||||||
/// The domain name which is expected in the TLS certificate from the server
|
|
||||||
pub(super) domain: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder for `TlsParameters`
|
/// Builder for `TlsParameters`
|
||||||
@@ -335,30 +316,20 @@ impl TlsParametersBuilder {
|
|||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||||
pub fn build_native(self) -> Result<TlsParameters, Error> {
|
pub fn build_native(self) -> Result<TlsParameters, Error> {
|
||||||
let mut tls_builder = TlsConnector::builder();
|
let cert_store = match self.cert_store {
|
||||||
|
CertificateStore::Default => super::native_tls::CertificateStore::System,
|
||||||
match self.cert_store {
|
CertificateStore::None => super::native_tls::CertificateStore::None,
|
||||||
CertificateStore::Default => {}
|
|
||||||
CertificateStore::None => {
|
|
||||||
tls_builder.disable_built_in_roots(true);
|
|
||||||
}
|
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
other => {
|
other => {
|
||||||
return Err(error::tls(format!(
|
return Err(error::tls(format!(
|
||||||
"{other:?} is not supported in native tls"
|
"{other:?} is not supported in native tls"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
for cert in self.root_certs {
|
|
||||||
tls_builder.add_root_certificate(cert.native_tls);
|
|
||||||
}
|
|
||||||
tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames);
|
|
||||||
tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs);
|
|
||||||
|
|
||||||
let min_tls_version = match self.min_tls_version {
|
let min_tls_version = match self.min_tls_version {
|
||||||
TlsVersion::Tlsv10 => Protocol::Tlsv10,
|
TlsVersion::Tlsv10 => super::native_tls::MinTlsVersion::Tlsv10,
|
||||||
TlsVersion::Tlsv11 => Protocol::Tlsv11,
|
TlsVersion::Tlsv11 => super::native_tls::MinTlsVersion::Tlsv11,
|
||||||
TlsVersion::Tlsv12 => Protocol::Tlsv12,
|
TlsVersion::Tlsv12 => super::native_tls::MinTlsVersion::Tlsv12,
|
||||||
TlsVersion::Tlsv13 => {
|
TlsVersion::Tlsv13 => {
|
||||||
return Err(error::tls(
|
return Err(error::tls(
|
||||||
"min tls version Tlsv13 not supported in native tls",
|
"min tls version Tlsv13 not supported in native tls",
|
||||||
@@ -366,225 +337,114 @@ impl TlsParametersBuilder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tls_builder.min_protocol_version(Some(min_tls_version));
|
let mut builder = super::TlsParametersBuilder::<super::NativeTls>::new(self.domain)
|
||||||
|
.certificate_store(cert_store)
|
||||||
|
.dangerous_accept_invalid_certs(self.accept_invalid_certs)
|
||||||
|
.dangerous_accept_invalid_hostnames(self.accept_invalid_hostnames)
|
||||||
|
.min_tls_version(min_tls_version);
|
||||||
|
for cert in self.root_certs {
|
||||||
|
builder = builder.add_root_certificate(cert.native_tls);
|
||||||
|
}
|
||||||
if let Some(identity) = self.identity {
|
if let Some(identity) = self.identity {
|
||||||
tls_builder.identity(identity.native_tls);
|
builder = builder.identify_with(identity.native_tls);
|
||||||
}
|
}
|
||||||
|
|
||||||
let connector = tls_builder.build().map_err(error::tls)?;
|
builder
|
||||||
Ok(TlsParameters {
|
.build()
|
||||||
connector: InnerTlsParameters::NativeTls { connector },
|
.map(super::NativeTls::__build_current_tls_parameters)
|
||||||
domain: self.domain,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` using boring-tls with the provided configuration
|
/// Creates a new `TlsParameters` using boring-tls with the provided configuration
|
||||||
|
///
|
||||||
|
/// Warning: this uses the certificate store passed via `certificate_store`
|
||||||
|
/// instead of the one configured in [`TlsParametersBuilder::certificate_store`].
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
pub fn build_boring(self) -> Result<TlsParameters, Error> {
|
pub fn build_boring(self) -> Result<TlsParameters, Error> {
|
||||||
use boring::ssl::{SslMethod, SslVerifyMode};
|
let cert_store = match self.cert_store {
|
||||||
|
CertificateStore::Default => super::boring_tls::CertificateStore::System,
|
||||||
let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
|
CertificateStore::None => super::boring_tls::CertificateStore::None,
|
||||||
|
|
||||||
if self.accept_invalid_certs {
|
|
||||||
tls_builder.set_verify(SslVerifyMode::NONE);
|
|
||||||
} else {
|
|
||||||
match self.cert_store {
|
|
||||||
CertificateStore::Default => {}
|
|
||||||
CertificateStore::None => {
|
|
||||||
// Replace the default store with an empty store.
|
|
||||||
tls_builder
|
|
||||||
.set_cert_store(X509StoreBuilder::new().map_err(error::tls)?.build());
|
|
||||||
}
|
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
other => {
|
other => {
|
||||||
return Err(error::tls(format!(
|
return Err(error::tls(format!(
|
||||||
"{other:?} is not supported in boring tls"
|
"{other:?} is not supported in native tls"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let cert_store = tls_builder.cert_store_mut();
|
|
||||||
|
|
||||||
for cert in self.root_certs {
|
|
||||||
cert_store.add_cert(cert.boring_tls).map_err(error::tls)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(identity) = self.identity {
|
|
||||||
tls_builder
|
|
||||||
.set_certificate(identity.boring_tls.0.as_ref())
|
|
||||||
.map_err(error::tls)?;
|
|
||||||
tls_builder
|
|
||||||
.set_private_key(identity.boring_tls.1.as_ref())
|
|
||||||
.map_err(error::tls)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let min_tls_version = match self.min_tls_version {
|
let min_tls_version = match self.min_tls_version {
|
||||||
TlsVersion::Tlsv10 => SslVersion::TLS1,
|
TlsVersion::Tlsv10 => super::boring_tls::MinTlsVersion::Tlsv10,
|
||||||
TlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
TlsVersion::Tlsv11 => super::boring_tls::MinTlsVersion::Tlsv11,
|
||||||
TlsVersion::Tlsv12 => SslVersion::TLS1_2,
|
TlsVersion::Tlsv12 => super::boring_tls::MinTlsVersion::Tlsv12,
|
||||||
TlsVersion::Tlsv13 => SslVersion::TLS1_3,
|
TlsVersion::Tlsv13 => super::boring_tls::MinTlsVersion::Tlsv13,
|
||||||
};
|
};
|
||||||
|
|
||||||
tls_builder
|
let mut builder = super::TlsParametersBuilder::<super::BoringTls>::new(self.domain)
|
||||||
.set_min_proto_version(Some(min_tls_version))
|
.certificate_store(cert_store)
|
||||||
.map_err(error::tls)?;
|
.dangerous_accept_invalid_certs(self.accept_invalid_certs)
|
||||||
let connector = tls_builder.build();
|
.dangerous_accept_invalid_hostnames(self.accept_invalid_hostnames)
|
||||||
Ok(TlsParameters {
|
.min_tls_version(min_tls_version);
|
||||||
connector: InnerTlsParameters::BoringTls {
|
for cert in self.root_certs {
|
||||||
connector,
|
builder = builder.add_root_certificate(cert.boring_tls);
|
||||||
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
}
|
||||||
},
|
if let Some(identity) = self.identity {
|
||||||
domain: self.domain,
|
builder = builder.identify_with(identity.boring_tls);
|
||||||
})
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
.build()
|
||||||
|
.map(super::BoringTls::__build_current_tls_parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` using rustls with the provided configuration
|
/// Creates a new `TlsParameters` using rustls with the provided configuration
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||||
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
|
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
|
||||||
let just_version3 = &[&rustls::version::TLS13];
|
let cert_store = match self.cert_store {
|
||||||
let supported_versions = match self.min_tls_version {
|
CertificateStore::Default => super::rustls::CertificateStore::default(),
|
||||||
|
#[cfg(feature = "webpki-roots")]
|
||||||
|
CertificateStore::WebpkiRoots => super::rustls::CertificateStore::WebpkiRoots,
|
||||||
|
CertificateStore::None => super::rustls::CertificateStore::None,
|
||||||
|
};
|
||||||
|
let min_tls_version = match self.min_tls_version {
|
||||||
TlsVersion::Tlsv10 => {
|
TlsVersion::Tlsv10 => {
|
||||||
return Err(error::tls("min tls version Tlsv10 not supported in rustls"))
|
return Err(error::tls("min tls version Tlsv10 not supported in rustls"))
|
||||||
}
|
}
|
||||||
TlsVersion::Tlsv11 => {
|
TlsVersion::Tlsv11 => {
|
||||||
return Err(error::tls("min tls version Tlsv11 not supported in rustls"))
|
return Err(error::tls("min tls version Tlsv11 not supported in rustls"))
|
||||||
}
|
}
|
||||||
TlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
|
TlsVersion::Tlsv12 => super::rustls::MinTlsVersion::Tlsv12,
|
||||||
TlsVersion::Tlsv13 => just_version3,
|
TlsVersion::Tlsv13 => super::rustls::MinTlsVersion::Tlsv13,
|
||||||
};
|
};
|
||||||
|
|
||||||
let crypto_provider = crate::rustls_crypto::crypto_provider();
|
let mut builder = super::TlsParametersBuilder::<super::Rustls>::new(self.domain)
|
||||||
let tls = ClientConfig::builder_with_provider(Arc::clone(&crypto_provider))
|
.certificate_store(cert_store)
|
||||||
.with_protocol_versions(supported_versions)
|
.dangerous_accept_invalid_certs(self.accept_invalid_certs)
|
||||||
.map_err(error::tls)?;
|
.dangerous_accept_invalid_hostnames(self.accept_invalid_hostnames)
|
||||||
|
.min_tls_version(min_tls_version);
|
||||||
// Build TLS config
|
|
||||||
let mut root_cert_store = RootCertStore::empty();
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "rustls-platform-verifier"),
|
|
||||||
feature = "rustls-native-certs"
|
|
||||||
))]
|
|
||||||
fn load_native_roots(store: &mut RootCertStore) {
|
|
||||||
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
|
||||||
rustls_native_certs::load_native_certs();
|
|
||||||
let errors_len = errors.len();
|
|
||||||
|
|
||||||
let (added, ignored) = store.add_parsable_certificates(certs);
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
tracing::debug!(
|
|
||||||
"loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
|
|
||||||
);
|
|
||||||
#[cfg(not(feature = "tracing"))]
|
|
||||||
let _ = (errors_len, added, ignored);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "rustls", feature = "webpki-roots"))]
|
|
||||||
fn load_webpki_roots(store: &mut RootCertStore) {
|
|
||||||
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(not(feature = "rustls-platform-verifier"), allow(unused_mut))]
|
|
||||||
let mut extra_roots = None::<Vec<CertificateDer<'static>>>;
|
|
||||||
match self.cert_store {
|
|
||||||
CertificateStore::Default => {
|
|
||||||
#[cfg(feature = "rustls-platform-verifier")]
|
|
||||||
{
|
|
||||||
extra_roots = Some(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "rustls-platform-verifier"),
|
|
||||||
feature = "rustls-native-certs"
|
|
||||||
))]
|
|
||||||
load_native_roots(&mut root_cert_store);
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "rustls-platform-verifier"),
|
|
||||||
not(feature = "rustls-native-certs"),
|
|
||||||
feature = "webpki-roots"
|
|
||||||
))]
|
|
||||||
load_webpki_roots(&mut root_cert_store);
|
|
||||||
}
|
|
||||||
#[cfg(all(feature = "rustls", feature = "webpki-roots"))]
|
|
||||||
CertificateStore::WebpkiRoots => {
|
|
||||||
load_webpki_roots(&mut root_cert_store);
|
|
||||||
}
|
|
||||||
CertificateStore::None => {}
|
|
||||||
}
|
|
||||||
for cert in self.root_certs {
|
for cert in self.root_certs {
|
||||||
for rustls_cert in cert.rustls {
|
for cert in cert.rustls {
|
||||||
#[cfg(feature = "rustls-platform-verifier")]
|
builder = builder.add_root_certificate(cert);
|
||||||
if let Some(extra_roots) = &mut extra_roots {
|
|
||||||
extra_roots.push(rustls_cert.clone());
|
|
||||||
}
|
}
|
||||||
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
|
||||||
}
|
}
|
||||||
|
if let Some(identity) = self.identity {
|
||||||
|
builder = builder.identify_with(identity.rustls_tls);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tls = if self.accept_invalid_certs
|
builder
|
||||||
|| (extra_roots.is_none() && self.accept_invalid_hostnames)
|
.build()
|
||||||
{
|
.map(super::Rustls::__build_current_tls_parameters)
|
||||||
let verifier = InvalidCertsVerifier {
|
|
||||||
ignore_invalid_hostnames: self.accept_invalid_hostnames,
|
|
||||||
ignore_invalid_certs: self.accept_invalid_certs,
|
|
||||||
roots: root_cert_store,
|
|
||||||
crypto_provider,
|
|
||||||
};
|
|
||||||
tls.dangerous()
|
|
||||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
|
||||||
} else {
|
|
||||||
#[cfg(feature = "rustls-platform-verifier")]
|
|
||||||
if let Some(extra_roots) = extra_roots {
|
|
||||||
tls.dangerous().with_custom_certificate_verifier(Arc::new(
|
|
||||||
rustls_platform_verifier::Verifier::new_with_extra_roots(
|
|
||||||
extra_roots,
|
|
||||||
crypto_provider,
|
|
||||||
)
|
|
||||||
.map_err(error::tls)?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
tls.with_root_certificates(root_cert_store)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "rustls-platform-verifier"))]
|
|
||||||
{
|
|
||||||
tls.with_root_certificates(root_cert_store)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tls = if let Some(identity) = self.identity {
|
|
||||||
let (client_certificates, private_key) = identity.rustls_tls;
|
|
||||||
tls.with_client_auth_cert(client_certificates, private_key)
|
|
||||||
.map_err(error::tls)?
|
|
||||||
} else {
|
|
||||||
tls.with_no_client_auth()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(TlsParameters {
|
|
||||||
connector: InnerTlsParameters::Rustls {
|
|
||||||
config: Arc::new(tls),
|
|
||||||
},
|
|
||||||
domain: self.domain,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub(crate) enum InnerTlsParameters {
|
pub(in crate::transport::smtp) enum InnerTlsParameters {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
NativeTls { connector: TlsConnector },
|
NativeTls(super::TlsParameters<super::NativeTls>),
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
Rustls { config: Arc<ClientConfig> },
|
Rustls(super::TlsParameters<super::Rustls>),
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
BoringTls {
|
BoringTls(super::TlsParameters<super::BoringTls>),
|
||||||
connector: SslConnector,
|
|
||||||
accept_invalid_hostnames: bool,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlsParameters {
|
impl TlsParameters {
|
||||||
@@ -596,7 +456,7 @@ impl TlsParameters {
|
|||||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||||
)]
|
)]
|
||||||
pub fn new(domain: String) -> Result<Self, Error> {
|
pub fn new(domain: String) -> Result<Self, Error> {
|
||||||
TlsParametersBuilder::new(domain).build()
|
Self::new_with::<super::DefaultTlsBackend>(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` builder
|
/// Creates a new `TlsParameters` builder
|
||||||
@@ -608,25 +468,38 @@ impl TlsParameters {
|
|||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||||
pub fn new_native(domain: String) -> Result<Self, Error> {
|
pub fn new_native(domain: String) -> Result<Self, Error> {
|
||||||
TlsParametersBuilder::new(domain).build_native()
|
Self::new_with::<super::NativeTls>(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` using rustls
|
/// Creates a new `TlsParameters` using rustls
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||||
pub fn new_rustls(domain: String) -> Result<Self, Error> {
|
pub fn new_rustls(domain: String) -> Result<Self, Error> {
|
||||||
TlsParametersBuilder::new(domain).build_rustls()
|
Self::new_with::<super::Rustls>(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` using boring
|
/// Creates a new `TlsParameters` using boring
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
pub fn new_boring(domain: String) -> Result<Self, Error> {
|
pub fn new_boring(domain: String) -> Result<Self, Error> {
|
||||||
TlsParametersBuilder::new(domain).build_boring()
|
Self::new_with::<super::BoringTls>(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with<B: TlsBackend>(domain: String) -> Result<Self, Error> {
|
||||||
|
super::TlsParametersBuilder::<B>::new(domain)
|
||||||
|
.build()
|
||||||
|
.map(B::__build_current_tls_parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn domain(&self) -> &str {
|
pub fn domain(&self) -> &str {
|
||||||
&self.domain
|
match self.inner {
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
InnerTlsParameters::NativeTls(ref inner) => &inner.server_name,
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
InnerTlsParameters::Rustls(ref inner) => inner.server_name.as_ref(),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerTlsParameters::BoringTls(ref inner) => &inner.server_name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,55 +518,36 @@ impl TlsParameters {
|
|||||||
)]
|
)]
|
||||||
pub struct Certificate {
|
pub struct Certificate {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
native_tls: native_tls::Certificate,
|
native_tls: super::native_tls::Certificate,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
rustls: Vec<CertificateDer<'static>>,
|
rustls: Vec<super::rustls::Certificate>,
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
boring_tls: boring::x509::X509,
|
boring_tls: super::boring_tls::Certificate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||||
impl Certificate {
|
impl Certificate {
|
||||||
/// Create a `Certificate` from a DER encoded certificate
|
/// Create a `Certificate` from a DER encoded certificate
|
||||||
pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
|
pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
|
||||||
#[cfg(feature = "native-tls")]
|
|
||||||
let native_tls_cert = native_tls::Certificate::from_der(&der).map_err(error::tls)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "boring-tls")]
|
|
||||||
let boring_tls_cert = boring::x509::X509::from_der(&der).map_err(error::tls)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
native_tls: native_tls_cert,
|
native_tls: super::native_tls::Certificate::from_der(&der)?,
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
rustls: vec![der.into()],
|
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
boring_tls: boring_tls_cert,
|
boring_tls: super::boring_tls::Certificate::from_der(&der)?,
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
rustls: vec![super::rustls::Certificate::from_der(der)],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `Certificate` from a PEM encoded certificate
|
/// Create a `Certificate` from a PEM encoded certificate
|
||||||
pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||||
#[cfg(feature = "native-tls")]
|
|
||||||
let native_tls_cert = native_tls::Certificate::from_pem(pem).map_err(error::tls)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "boring-tls")]
|
|
||||||
let boring_tls_cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
let rustls_cert = {
|
|
||||||
CertificateDer::pem_slice_iter(pem)
|
|
||||||
.collect::<Result<Vec<_>, pki_types::pem::Error>>()
|
|
||||||
.map_err(|_| error::tls("invalid certificates"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
native_tls: native_tls_cert,
|
native_tls: super::native_tls::Certificate::from_pem(pem)?,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
rustls: rustls_cert,
|
rustls: super::rustls::Certificate::from_pem_bundle(pem)?,
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
boring_tls: boring_tls_cert,
|
boring_tls: super::boring_tls::Certificate::from_pem(pem)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,6 +559,7 @@ impl Debug for Certificate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An identity that can be used with [`TlsParametersBuilder::identify_with`]
|
/// An identity that can be used with [`TlsParametersBuilder::identify_with`]
|
||||||
|
#[derive(Clone)]
|
||||||
#[allow(missing_copy_implementations)]
|
#[allow(missing_copy_implementations)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||||
@@ -718,11 +573,11 @@ impl Debug for Certificate {
|
|||||||
)]
|
)]
|
||||||
pub struct Identity {
|
pub struct Identity {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
native_tls: native_tls::Identity,
|
native_tls: super::native_tls::Identity,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
rustls_tls: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
|
rustls_tls: super::rustls::Identity,
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
boring_tls: (boring::x509::X509, PKey<boring::pkey::Private>),
|
boring_tls: super::boring_tls::Identity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Identity {
|
impl Debug for Identity {
|
||||||
@@ -731,132 +586,16 @@ impl Debug for Identity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Identity {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Identity {
|
|
||||||
#[cfg(feature = "native-tls")]
|
|
||||||
native_tls: self.native_tls.clone(),
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
rustls_tls: (self.rustls_tls.0.clone(), self.rustls_tls.1.clone_key()),
|
|
||||||
#[cfg(feature = "boring-tls")]
|
|
||||||
boring_tls: (self.boring_tls.0.clone(), self.boring_tls.1.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||||
impl Identity {
|
impl Identity {
|
||||||
pub fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
pub fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
native_tls: Identity::from_pem_native_tls(pem, key)?,
|
native_tls: super::native_tls::Identity::from_pem(pem, key)?,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
rustls_tls: Identity::from_pem_rustls_tls(pem, key)?,
|
rustls_tls: super::rustls::Identity::from_pem(pem, key)?,
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
boring_tls: Identity::from_pem_boring_tls(pem, key)?,
|
boring_tls: super::boring_tls::Identity::from_pem(pem, key)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "native-tls")]
|
|
||||||
fn from_pem_native_tls(pem: &[u8], key: &[u8]) -> Result<native_tls::Identity, Error> {
|
|
||||||
native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
fn from_pem_rustls_tls(
|
|
||||||
pem: &[u8],
|
|
||||||
key: &[u8],
|
|
||||||
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
|
|
||||||
let key = match PrivateKeyDer::from_pem_slice(key) {
|
|
||||||
Ok(key) => key,
|
|
||||||
Err(pki_types::pem::Error::NoItemsFound) => {
|
|
||||||
return Err(error::tls("no private key found"))
|
|
||||||
}
|
|
||||||
Err(err) => return Err(error::tls(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((vec![pem.to_owned().into()], key))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "boring-tls")]
|
|
||||||
fn from_pem_boring_tls(
|
|
||||||
pem: &[u8],
|
|
||||||
key: &[u8],
|
|
||||||
) -> Result<(boring::x509::X509, PKey<boring::pkey::Private>), Error> {
|
|
||||||
let cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
|
||||||
let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
|
|
||||||
Ok((cert, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct InvalidCertsVerifier {
|
|
||||||
ignore_invalid_hostnames: bool,
|
|
||||||
ignore_invalid_certs: bool,
|
|
||||||
roots: RootCertStore,
|
|
||||||
crypto_provider: Arc<CryptoProvider>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
|
||||||
impl ServerCertVerifier for InvalidCertsVerifier {
|
|
||||||
fn verify_server_cert(
|
|
||||||
&self,
|
|
||||||
end_entity: &CertificateDer<'_>,
|
|
||||||
intermediates: &[CertificateDer<'_>],
|
|
||||||
server_name: &ServerName<'_>,
|
|
||||||
_ocsp_response: &[u8],
|
|
||||||
now: UnixTime,
|
|
||||||
) -> Result<ServerCertVerified, TlsError> {
|
|
||||||
let cert = ParsedCertificate::try_from(end_entity)?;
|
|
||||||
|
|
||||||
if !self.ignore_invalid_certs {
|
|
||||||
rustls::client::verify_server_cert_signed_by_trust_anchor(
|
|
||||||
&cert,
|
|
||||||
&self.roots,
|
|
||||||
intermediates,
|
|
||||||
now,
|
|
||||||
self.crypto_provider.signature_verification_algorithms.all,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.ignore_invalid_hostnames {
|
|
||||||
rustls::client::verify_server_name(&cert, server_name)?;
|
|
||||||
}
|
|
||||||
Ok(ServerCertVerified::assertion())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_tls12_signature(
|
|
||||||
&self,
|
|
||||||
message: &[u8],
|
|
||||||
cert: &CertificateDer<'_>,
|
|
||||||
dss: &DigitallySignedStruct,
|
|
||||||
) -> Result<HandshakeSignatureValid, TlsError> {
|
|
||||||
verify_tls12_signature(
|
|
||||||
message,
|
|
||||||
cert,
|
|
||||||
dss,
|
|
||||||
&self.crypto_provider.signature_verification_algorithms,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_tls13_signature(
|
|
||||||
&self,
|
|
||||||
message: &[u8],
|
|
||||||
cert: &CertificateDer<'_>,
|
|
||||||
dss: &DigitallySignedStruct,
|
|
||||||
) -> Result<HandshakeSignatureValid, TlsError> {
|
|
||||||
verify_tls13_signature(
|
|
||||||
message,
|
|
||||||
cert,
|
|
||||||
dss,
|
|
||||||
&self.crypto_provider.signature_verification_algorithms,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
|
||||||
self.crypto_provider
|
|
||||||
.signature_verification_algorithms
|
|
||||||
.supported_schemes()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
253
src/transport/smtp/client/tls/mod.rs
Normal file
253
src/transport/smtp/client/tls/mod.rs
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
use crate::transport::smtp::Error;
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
|
pub(super) mod boring_tls;
|
||||||
|
pub(super) mod current;
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||||
|
pub(super) mod native_tls;
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||||
|
pub(super) mod rustls;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
pub(in crate::transport::smtp) struct TlsParameters<B: TlsBackend> {
|
||||||
|
pub(in crate::transport::smtp) server_name: B::ServerName,
|
||||||
|
pub(in crate::transport::smtp) connector: B::Connector,
|
||||||
|
pub(in crate::transport::smtp) extra_info: B::ExtraInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: TlsBackend> Clone for TlsParameters<B> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
server_name: self.server_name.clone(),
|
||||||
|
connector: self.connector.clone(),
|
||||||
|
extra_info: self.extra_info.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TlsParametersBuilder<B: TlsBackend> {
|
||||||
|
server_name: String,
|
||||||
|
cert_store: B::CertificateStore,
|
||||||
|
root_certs: Vec<B::Certificate>,
|
||||||
|
identity: Option<B::Identity>,
|
||||||
|
accept_invalid_certs: bool,
|
||||||
|
accept_invalid_hostnames: bool,
|
||||||
|
min_tls_version: B::MinTlsVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: TlsBackend> TlsParametersBuilder<B> {
|
||||||
|
fn new(server_name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
server_name,
|
||||||
|
cert_store: Default::default(),
|
||||||
|
root_certs: Vec::new(),
|
||||||
|
identity: None,
|
||||||
|
accept_invalid_certs: false,
|
||||||
|
accept_invalid_hostnames: false,
|
||||||
|
min_tls_version: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn certificate_store(mut self, cert_store: B::CertificateStore) -> Self {
|
||||||
|
self.cert_store = cert_store;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_root_certificate(mut self, cert: B::Certificate) -> Self {
|
||||||
|
self.root_certs.push(cert);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identify_with(mut self, identity: B::Identity) -> Self {
|
||||||
|
self.identity = Some(identity);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_tls_version(mut self, min_tls_version: B::MinTlsVersion) -> Self {
|
||||||
|
self.min_tls_version = min_tls_version;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
|
||||||
|
self.accept_invalid_certs = accept_invalid_certs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
|
||||||
|
self.accept_invalid_hostnames = accept_invalid_hostnames;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(self) -> Result<TlsParameters<B>, Error> {
|
||||||
|
B::__build_connector(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
trait TlsBackend: private::SealedTlsBackend {
|
||||||
|
type CertificateStore: Default;
|
||||||
|
type Certificate;
|
||||||
|
type Identity;
|
||||||
|
type MinTlsVersion: Default;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn __build_connector(builder: TlsParametersBuilder<Self>)
|
||||||
|
-> Result<TlsParameters<Self>, Error>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
type DefaultTlsBackend = NativeTls;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "rustls", not(feature = "native-tls")))]
|
||||||
|
type DefaultTlsBackend = Rustls;
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "boring-tls",
|
||||||
|
not(feature = "native-tls"),
|
||||||
|
not(feature = "rustls")
|
||||||
|
))]
|
||||||
|
type DefaultTlsBackend = BoringTls;
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(missing_copy_implementations)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(in crate::transport::smtp) struct NativeTls;
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||||
|
impl TlsBackend for NativeTls {
|
||||||
|
type CertificateStore = self::native_tls::CertificateStore;
|
||||||
|
type Certificate = self::native_tls::Certificate;
|
||||||
|
type Identity = self::native_tls::Identity;
|
||||||
|
type MinTlsVersion = self::native_tls::MinTlsVersion;
|
||||||
|
|
||||||
|
fn __build_connector(
|
||||||
|
builder: TlsParametersBuilder<Self>,
|
||||||
|
) -> Result<TlsParameters<Self>, Error> {
|
||||||
|
self::native_tls::build_connector(builder).map(|(server_name, connector)| TlsParameters {
|
||||||
|
server_name,
|
||||||
|
connector,
|
||||||
|
extra_info: (),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
|
||||||
|
self::current::TlsParameters {
|
||||||
|
inner: self::current::InnerTlsParameters::NativeTls(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(missing_copy_implementations)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(in crate::transport::smtp) struct Rustls;
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||||
|
impl TlsBackend for Rustls {
|
||||||
|
type CertificateStore = self::rustls::CertificateStore;
|
||||||
|
type Certificate = self::rustls::Certificate;
|
||||||
|
type Identity = self::rustls::Identity;
|
||||||
|
type MinTlsVersion = self::rustls::MinTlsVersion;
|
||||||
|
|
||||||
|
fn __build_connector(
|
||||||
|
builder: TlsParametersBuilder<Self>,
|
||||||
|
) -> Result<TlsParameters<Self>, Error> {
|
||||||
|
self::rustls::build_connector(builder).map(|(server_name, connector)| TlsParameters {
|
||||||
|
server_name,
|
||||||
|
connector,
|
||||||
|
extra_info: (),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
|
||||||
|
self::current::TlsParameters {
|
||||||
|
inner: self::current::InnerTlsParameters::Rustls(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(missing_copy_implementations)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(in crate::transport::smtp) struct BoringTls;
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
|
impl TlsBackend for BoringTls {
|
||||||
|
type CertificateStore = self::boring_tls::CertificateStore;
|
||||||
|
type Certificate = self::boring_tls::Certificate;
|
||||||
|
type Identity = self::boring_tls::Identity;
|
||||||
|
type MinTlsVersion = self::boring_tls::MinTlsVersion;
|
||||||
|
|
||||||
|
fn __build_connector(
|
||||||
|
builder: TlsParametersBuilder<Self>,
|
||||||
|
) -> Result<TlsParameters<Self>, Error> {
|
||||||
|
let accept_invalid_hostnames = builder.accept_invalid_hostnames;
|
||||||
|
self::boring_tls::build_connector(builder).map(|(server_name, connector)| TlsParameters {
|
||||||
|
server_name,
|
||||||
|
connector,
|
||||||
|
extra_info: BoringTlsExtraInfo {
|
||||||
|
accept_invalid_hostnames,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
|
||||||
|
self::current::TlsParameters {
|
||||||
|
inner: self::current::InnerTlsParameters::BoringTls(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(in crate::transport::smtp) struct BoringTlsExtraInfo {
|
||||||
|
pub(super) accept_invalid_hostnames: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
pub(in crate::transport::smtp) trait SealedTlsBackend:
|
||||||
|
Sized
|
||||||
|
{
|
||||||
|
type ServerName: Clone + AsRef<str>;
|
||||||
|
type Connector: Clone;
|
||||||
|
type ExtraInfo: Clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
impl SealedTlsBackend for super::NativeTls {
|
||||||
|
type ServerName = Box<str>;
|
||||||
|
type Connector = native_tls::TlsConnector;
|
||||||
|
type ExtraInfo = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
impl SealedTlsBackend for super::Rustls {
|
||||||
|
type ServerName = super::rustls::ServerName;
|
||||||
|
type Connector = std::sync::Arc<rustls::client::ClientConfig>;
|
||||||
|
type ExtraInfo = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
impl SealedTlsBackend for super::BoringTls {
|
||||||
|
type ServerName = Box<str>;
|
||||||
|
type Connector = boring::ssl::SslConnector;
|
||||||
|
type ExtraInfo = super::BoringTlsExtraInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/transport/smtp/client/tls/native_tls.rs
Normal file
95
src/transport/smtp/client/tls/native_tls.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
use native_tls::TlsConnector;
|
||||||
|
|
||||||
|
use crate::transport::smtp::error::{self, Error};
|
||||||
|
|
||||||
|
pub(super) fn build_connector(
|
||||||
|
builder: super::TlsParametersBuilder<super::NativeTls>,
|
||||||
|
) -> Result<(Box<str>, TlsConnector), Error> {
|
||||||
|
let mut tls_builder = TlsConnector::builder();
|
||||||
|
|
||||||
|
match builder.cert_store {
|
||||||
|
CertificateStore::System => {}
|
||||||
|
CertificateStore::None => {
|
||||||
|
tls_builder.disable_built_in_roots(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for cert in builder.root_certs {
|
||||||
|
tls_builder.add_root_certificate(cert.0);
|
||||||
|
}
|
||||||
|
tls_builder.danger_accept_invalid_hostnames(builder.accept_invalid_hostnames);
|
||||||
|
tls_builder.danger_accept_invalid_certs(builder.accept_invalid_certs);
|
||||||
|
|
||||||
|
let min_tls_version = match builder.min_tls_version {
|
||||||
|
MinTlsVersion::Tlsv10 => native_tls::Protocol::Tlsv10,
|
||||||
|
MinTlsVersion::Tlsv11 => native_tls::Protocol::Tlsv11,
|
||||||
|
MinTlsVersion::Tlsv12 => native_tls::Protocol::Tlsv12,
|
||||||
|
};
|
||||||
|
|
||||||
|
tls_builder.min_protocol_version(Some(min_tls_version));
|
||||||
|
if let Some(identity) = builder.identity {
|
||||||
|
tls_builder.identity(identity.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let connector = tls_builder.build().map_err(error::tls)?;
|
||||||
|
Ok((builder.server_name.into_boxed_str(), connector))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[allow(missing_copy_implementations)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(super) enum CertificateStore {
|
||||||
|
#[default]
|
||||||
|
System,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct Certificate(pub(super) native_tls::Certificate);
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
pub(super) fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self(
|
||||||
|
native_tls::Certificate::from_pem(pem).map_err(error::tls)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_der(der: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self(
|
||||||
|
native_tls::Certificate::from_der(der).map_err(error::tls)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Certificate {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Certificate").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct Identity(pub(super) native_tls::Identity);
|
||||||
|
|
||||||
|
impl Identity {
|
||||||
|
pub(super) fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self(
|
||||||
|
native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Identity {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Identity").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(super) enum MinTlsVersion {
|
||||||
|
Tlsv10,
|
||||||
|
Tlsv11,
|
||||||
|
#[default]
|
||||||
|
Tlsv12,
|
||||||
|
}
|
||||||
329
src/transport/smtp/client/tls/rustls.rs
Normal file
329
src/transport/smtp/client/tls/rustls.rs
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::{self, Debug},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rustls::{
|
||||||
|
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||||
|
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
|
||||||
|
pki_types::{self, UnixTime},
|
||||||
|
server::ParsedCertificate,
|
||||||
|
ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::transport::smtp::error::{self, Error};
|
||||||
|
|
||||||
|
pub(super) fn build_connector(
|
||||||
|
builder: super::TlsParametersBuilder<super::Rustls>,
|
||||||
|
) -> Result<(ServerName, Arc<ClientConfig>), Error> {
|
||||||
|
let just_version3 = &[&rustls::version::TLS13];
|
||||||
|
let supported_versions = match builder.min_tls_version {
|
||||||
|
MinTlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
|
||||||
|
MinTlsVersion::Tlsv13 => just_version3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let crypto_provider = crate::rustls_crypto::crypto_provider();
|
||||||
|
let tls = ClientConfig::builder_with_provider(Arc::clone(&crypto_provider))
|
||||||
|
.with_protocol_versions(supported_versions)
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
|
||||||
|
// Build TLS config
|
||||||
|
let mut root_cert_store = RootCertStore::empty();
|
||||||
|
#[cfg(feature = "rustls-platform-verifier")]
|
||||||
|
let mut extra_roots = Vec::new();
|
||||||
|
|
||||||
|
match builder.cert_store {
|
||||||
|
#[cfg(feature = "rustls-platform-verifier")]
|
||||||
|
CertificateStore::PlatformVerifier => {
|
||||||
|
extra_roots = builder
|
||||||
|
.root_certs
|
||||||
|
.iter()
|
||||||
|
.map(|cert| cert.0.clone())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rustls-native-certs")]
|
||||||
|
CertificateStore::NativeCerts => {
|
||||||
|
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||||
|
rustls_native_certs::load_native_certs();
|
||||||
|
let errors_len = errors.len();
|
||||||
|
|
||||||
|
let (added, ignored) = root_cert_store.add_parsable_certificates(certs);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::debug!(
|
||||||
|
"loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "tracing"))]
|
||||||
|
let _ = (errors_len, added, ignored);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "webpki-roots")]
|
||||||
|
CertificateStore::WebpkiRoots => {
|
||||||
|
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||||
|
}
|
||||||
|
CertificateStore::None => {}
|
||||||
|
}
|
||||||
|
for cert in builder.root_certs {
|
||||||
|
root_cert_store.add(cert.0).map_err(error::tls)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tls = match (
|
||||||
|
builder.cert_store,
|
||||||
|
builder.accept_invalid_certs,
|
||||||
|
builder.accept_invalid_hostnames,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "rustls-platform-verifier")]
|
||||||
|
(CertificateStore::PlatformVerifier, false, _) => {
|
||||||
|
tls.dangerous().with_custom_certificate_verifier(Arc::new(
|
||||||
|
rustls_platform_verifier::Verifier::new_with_extra_roots(
|
||||||
|
extra_roots,
|
||||||
|
crypto_provider,
|
||||||
|
)
|
||||||
|
.map_err(error::tls)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(_, true, _) | (_, _, true) => {
|
||||||
|
let verifier = InvalidCertsVerifier {
|
||||||
|
ignore_invalid_hostnames: builder.accept_invalid_hostnames,
|
||||||
|
ignore_invalid_certs: builder.accept_invalid_certs,
|
||||||
|
roots: root_cert_store,
|
||||||
|
crypto_provider,
|
||||||
|
};
|
||||||
|
tls.dangerous()
|
||||||
|
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||||
|
}
|
||||||
|
_ => tls.with_root_certificates(root_cert_store),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tls = if let Some(identity) = builder.identity {
|
||||||
|
tls.with_client_auth_cert(identity.chain, identity.key)
|
||||||
|
.map_err(error::tls)?
|
||||||
|
} else {
|
||||||
|
tls.with_no_client_auth()
|
||||||
|
};
|
||||||
|
let server_name = ServerName::try_from(builder.server_name)?;
|
||||||
|
Ok((server_name, Arc::new(tls)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(in crate::transport::smtp) struct ServerName {
|
||||||
|
val: pki_types::ServerName<'static>,
|
||||||
|
str_val: Box<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerName {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(in crate::transport::smtp) fn inner(self) -> pki_types::ServerName<'static> {
|
||||||
|
self.val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::transport::smtp) fn inner_ref(&self) -> &pki_types::ServerName<'static> {
|
||||||
|
&self.val
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, crate::transport::smtp::Error> {
|
||||||
|
let val: pki_types::ServerName<'_> = value
|
||||||
|
.as_str()
|
||||||
|
.try_into()
|
||||||
|
.map_err(crate::transport::smtp::error::tls)?;
|
||||||
|
Ok(Self {
|
||||||
|
val: val.to_owned(),
|
||||||
|
str_val: value.into_boxed_str(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for ServerName {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.str_val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[allow(dead_code, missing_copy_implementations)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(super) enum CertificateStore {
|
||||||
|
#[cfg(feature = "rustls-platform-verifier")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-platform-verifier")))]
|
||||||
|
#[cfg_attr(feature = "rustls-platform-verifier", default)]
|
||||||
|
PlatformVerifier,
|
||||||
|
#[cfg(feature = "rustls-native-certs")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(
|
||||||
|
not(feature = "rustls-platform-verifier"),
|
||||||
|
feature = "rustls-native-certs",
|
||||||
|
),
|
||||||
|
default
|
||||||
|
)]
|
||||||
|
NativeCerts,
|
||||||
|
#[cfg(feature = "webpki-roots")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(
|
||||||
|
not(feature = "rustls-platform-verifier"),
|
||||||
|
not(feature = "rustls-native-certs"),
|
||||||
|
feature = "webpki-roots",
|
||||||
|
),
|
||||||
|
default
|
||||||
|
)]
|
||||||
|
WebpkiRoots,
|
||||||
|
#[cfg_attr(
|
||||||
|
all(
|
||||||
|
not(feature = "webpki-roots"),
|
||||||
|
not(feature = "rustls-platform-verifier"),
|
||||||
|
not(feature = "rustls-native-certs")
|
||||||
|
),
|
||||||
|
default
|
||||||
|
)]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct Certificate(pub(super) pki_types::CertificateDer<'static>);
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(super) fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||||
|
use rustls::pki_types::pem::PemObject as _;
|
||||||
|
|
||||||
|
Ok(Self(
|
||||||
|
pki_types::CertificateDer::from_pem_slice(pem)
|
||||||
|
.map_err(|_| error::tls("invalid certificate"))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_pem_bundle(pem: &[u8]) -> Result<Vec<Self>, Error> {
|
||||||
|
use rustls::pki_types::pem::PemObject as _;
|
||||||
|
|
||||||
|
pki_types::CertificateDer::pem_slice_iter(pem)
|
||||||
|
.map(|cert| Ok(Self(cert?)))
|
||||||
|
.collect::<Result<Vec<_>, pki_types::pem::Error>>()
|
||||||
|
.map_err(|_| error::tls("invalid certificate"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_der(der: Vec<u8>) -> Self {
|
||||||
|
Self(der.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Certificate {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Certificate").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct Identity {
|
||||||
|
pub(super) chain: Vec<pki_types::CertificateDer<'static>>,
|
||||||
|
pub(super) key: pki_types::PrivateKeyDer<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Identity {
|
||||||
|
pub(super) fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||||
|
use rustls::pki_types::pem::PemObject as _;
|
||||||
|
|
||||||
|
let key = match pki_types::PrivateKeyDer::from_pem_slice(key) {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(pki_types::pem::Error::NoItemsFound) => {
|
||||||
|
return Err(error::tls("no private key found"))
|
||||||
|
}
|
||||||
|
Err(err) => return Err(error::tls(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
chain: vec![pem.to_owned().into()],
|
||||||
|
key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Identity {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
chain: self.chain.clone(),
|
||||||
|
key: self.key.clone_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Identity {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Identity").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub(super) enum MinTlsVersion {
|
||||||
|
#[default]
|
||||||
|
Tlsv12,
|
||||||
|
Tlsv13,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InvalidCertsVerifier {
|
||||||
|
ignore_invalid_hostnames: bool,
|
||||||
|
ignore_invalid_certs: bool,
|
||||||
|
roots: RootCertStore,
|
||||||
|
crypto_provider: Arc<CryptoProvider>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerCertVerifier for InvalidCertsVerifier {
|
||||||
|
fn verify_server_cert(
|
||||||
|
&self,
|
||||||
|
end_entity: &pki_types::CertificateDer<'_>,
|
||||||
|
intermediates: &[pki_types::CertificateDer<'_>],
|
||||||
|
server_name: &pki_types::ServerName<'_>,
|
||||||
|
_ocsp_response: &[u8],
|
||||||
|
now: UnixTime,
|
||||||
|
) -> Result<ServerCertVerified, rustls::Error> {
|
||||||
|
let cert = ParsedCertificate::try_from(end_entity)?;
|
||||||
|
|
||||||
|
if !self.ignore_invalid_certs {
|
||||||
|
rustls::client::verify_server_cert_signed_by_trust_anchor(
|
||||||
|
&cert,
|
||||||
|
&self.roots,
|
||||||
|
intermediates,
|
||||||
|
now,
|
||||||
|
self.crypto_provider.signature_verification_algorithms.all,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.ignore_invalid_hostnames {
|
||||||
|
rustls::client::verify_server_name(&cert, server_name)?;
|
||||||
|
}
|
||||||
|
Ok(ServerCertVerified::assertion())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls12_signature(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
cert: &pki_types::CertificateDer<'_>,
|
||||||
|
dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||||
|
verify_tls12_signature(
|
||||||
|
message,
|
||||||
|
cert,
|
||||||
|
dss,
|
||||||
|
&self.crypto_provider.signature_verification_algorithms,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls13_signature(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
cert: &pki_types::CertificateDer<'_>,
|
||||||
|
dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||||
|
verify_tls13_signature(
|
||||||
|
message,
|
||||||
|
cert,
|
||||||
|
dss,
|
||||||
|
&self.crypto_provider.signature_verification_algorithms,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||||
|
self.crypto_provider
|
||||||
|
.signature_verification_algorithms
|
||||||
|
.supported_schemes()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user