This commit is contained in:
Paolo Barbolini
2025-05-02 08:29:20 +02:00
parent d31490a2a9
commit 2b36935b1f
7 changed files with 228 additions and 136 deletions

View File

@@ -14,8 +14,6 @@ use futures_io::{
};
#[cfg(feature = "async-std1-rustls")]
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")]
use tokio1_boring::SslStream as Tokio1SslStream;
#[cfg(feature = "tokio1")]
@@ -319,11 +317,9 @@ impl AsyncNetworkStream {
tcp_stream: Box<dyn AsyncTokioStream>,
tls_parameters: TlsParameters,
) -> Result<InnerAsyncNetworkStream, Error> {
let domain = tls_parameters.domain().to_owned();
match tls_parameters.connector {
match tls_parameters.inner {
#[cfg(feature = "native-tls")]
InnerTlsParameters::NativeTls { connector } => {
InnerTlsParameters::NativeTls(inner) => {
#[cfg(not(feature = "tokio1-native-tls"))]
panic!("built without the tokio1-native-tls feature");
@@ -331,16 +327,16 @@ impl AsyncNetworkStream {
return {
use tokio1_native_tls_crate::TlsConnector;
let connector = TlsConnector::from(connector);
let connector = TlsConnector::from(inner.connector);
let stream = connector
.connect(&domain, tcp_stream)
.connect(&inner.server_name, tcp_stream)
.await
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::Tokio1NativeTls(stream))
};
}
#[cfg(feature = "rustls")]
InnerTlsParameters::Rustls { config } => {
InnerTlsParameters::Rustls(inner) => {
#[cfg(not(feature = "tokio1-rustls"))]
panic!("built without the tokio1-rustls feature");
@@ -348,31 +344,25 @@ impl AsyncNetworkStream {
return {
use tokio1_rustls::TlsConnector;
let domain = ServerName::try_from(domain.as_str())
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
let connector = TlsConnector::from(config);
let connector = TlsConnector::from(inner.connector);
let stream = connector
.connect(domain.to_owned(), tcp_stream)
.connect(inner.server_name.inner(), tcp_stream)
.await
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::Tokio1Rustls(stream))
};
}
#[cfg(feature = "boring-tls")]
InnerTlsParameters::BoringTls {
connector,
accept_invalid_hostnames,
} => {
InnerTlsParameters::BoringTls(inner) => {
#[cfg(not(feature = "tokio1-boring-tls"))]
panic!("built without the tokio1-boring-tls feature");
#[cfg(feature = "tokio1-boring-tls")]
return {
let mut config = connector.configure().map_err(error::connection)?;
config.set_verify_hostname(accept_invalid_hostnames);
let mut config = inner.connector.configure().map_err(error::connection)?;
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
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::Tokio1BoringTls(stream))

View File

@@ -12,7 +12,7 @@ use boring::ssl::SslStream;
#[cfg(feature = "native-tls")]
use native_tls::TlsStream;
#[cfg(feature = "rustls")]
use rustls::{pki_types::ServerName, ClientConnection, StreamOwned};
use rustls::{ClientConnection, StreamOwned};
use socket2::{Domain, Protocol, Type};
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
@@ -172,33 +172,33 @@ impl NetworkStream {
tcp_stream: TcpStream,
tls_parameters: &TlsParameters,
) -> Result<InnerNetworkStream, Error> {
Ok(match &tls_parameters.connector {
Ok(match &tls_parameters.inner {
#[cfg(feature = "native-tls")]
InnerTlsParameters::NativeTls { connector } => {
let stream = connector
.connect(tls_parameters.domain(), tcp_stream)
InnerTlsParameters::NativeTls(inner) => {
let stream = inner
.connector
.connect(&inner.server_name, tcp_stream)
.map_err(error::connection)?;
InnerNetworkStream::NativeTls(stream)
}
#[cfg(feature = "rustls")]
InnerTlsParameters::Rustls { config } => {
let domain = ServerName::try_from(tls_parameters.domain())
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
let connection = ClientConnection::new(Arc::clone(config), domain.to_owned())
.map_err(error::connection)?;
InnerTlsParameters::Rustls(inner) => {
let connection = ClientConnection::new(
Arc::clone(&inner.connector),
inner.server_name.inner_ref().clone(),
)
.map_err(error::connection)?;
let stream = StreamOwned::new(connection, tcp_stream);
InnerNetworkStream::Rustls(stream)
}
#[cfg(feature = "boring-tls")]
InnerTlsParameters::BoringTls {
connector,
accept_invalid_hostnames,
} => {
let stream = connector
InnerTlsParameters::BoringTls(inner) => {
let stream = inner
.connector
.configure()
.map_err(error::connection)?
.verify_hostname(*accept_invalid_hostnames)
.connect(tls_parameters.domain(), tcp_stream)
.verify_hostname(inner.extra_info.accept_invalid_hostnames)
.connect(&inner.server_name, tcp_stream)
.map_err(error::connection)?;
InnerNetworkStream::BoringTls(stream)
}

View File

@@ -9,7 +9,7 @@ use crate::transport::smtp::error::{self, Error};
pub(super) fn build_connector(
builder: super::TlsParametersBuilder<super::BoringTls>,
) -> Result<SslConnector, Error> {
) -> Result<(Box<str>, SslConnector), Error> {
let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
if builder.accept_invalid_certs {
@@ -49,7 +49,7 @@ pub(super) fn build_connector(
tls_builder
.set_min_proto_version(Some(min_tls_version))
.map_err(error::tls)?;
Ok(tls_builder.build())
Ok((builder.server_name.into_boxed_str(), tls_builder.build()))
}
#[derive(Debug, Clone, Default)]

View File

@@ -1,5 +1,6 @@
use std::fmt::{self, Debug};
use super::TlsBackend;
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
use crate::transport::smtp::{error, Error};
@@ -143,9 +144,7 @@ pub enum CertificateStore {
/// Parameters to use for secure clients
#[derive(Clone)]
pub struct TlsParameters {
pub(crate) connector: InnerTlsParameters,
/// The domain name which is expected in the TLS certificate from the server
pub(super) domain: String,
pub(in crate::transport::smtp) inner: InnerTlsParameters,
}
/// Builder for `TlsParameters`
@@ -306,7 +305,7 @@ impl TlsParametersBuilder {
builder = builder.identify_with(identity.native_tls);
}
builder.build_legacy()
builder.build().map(TlsParameters::from_inner)
}
/// Creates a new `TlsParameters` using boring-tls with the provided configuration
@@ -345,7 +344,7 @@ impl TlsParametersBuilder {
builder = builder.identify_with(identity.boring_tls);
}
builder.build_legacy()
builder.build().map(TlsParameters::from_inner)
}
/// Creates a new `TlsParameters` using rustls with the provided configuration
@@ -386,24 +385,19 @@ impl TlsParametersBuilder {
builder = builder.identify_with(identity.rustls_tls);
}
builder.build_legacy()
builder.build().map(TlsParameters::from_inner)
}
}
#[derive(Clone)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum InnerTlsParameters {
pub(in crate::transport::smtp) enum InnerTlsParameters {
#[cfg(feature = "native-tls")]
NativeTls { connector: native_tls::TlsConnector },
NativeTls(super::TlsParameters<super::NativeTls>),
#[cfg(feature = "rustls")]
Rustls {
config: std::sync::Arc<rustls::ClientConfig>,
},
Rustls(super::TlsParameters<super::Rustls>),
#[cfg(feature = "boring-tls")]
BoringTls {
connector: boring::ssl::SslConnector,
accept_invalid_hostnames: bool,
},
BoringTls(super::TlsParameters<super::BoringTls>),
}
impl TlsParameters {
@@ -415,7 +409,9 @@ impl TlsParameters {
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
)]
pub fn new(domain: String) -> Result<Self, Error> {
TlsParametersBuilder::new(domain).build()
super::TlsParametersBuilder::<super::DefaultTlsBackend>::new(domain)
.build()
.map(Self::from_inner)
}
/// Creates a new `TlsParameters` builder
@@ -427,25 +423,42 @@ impl TlsParameters {
#[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
pub fn new_native(domain: String) -> Result<Self, Error> {
TlsParametersBuilder::new(domain).build_native()
super::TlsParametersBuilder::<super::NativeTls>::new(domain)
.build()
.map(Self::from_inner)
}
/// Creates a new `TlsParameters` using rustls
#[cfg(feature = "rustls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
pub fn new_rustls(domain: String) -> Result<Self, Error> {
TlsParametersBuilder::new(domain).build_rustls()
super::TlsParametersBuilder::<super::Rustls>::new(domain)
.build()
.map(Self::from_inner)
}
/// Creates a new `TlsParameters` using boring
#[cfg(feature = "boring-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
pub fn new_boring(domain: String) -> Result<Self, Error> {
TlsParametersBuilder::new(domain).build_boring()
super::TlsParametersBuilder::<super::BoringTls>::new(domain)
.build()
.map(Self::from_inner)
}
fn from_inner<B: TlsBackend>(inner: super::TlsParameters<B>) -> Self {
B::__build_current_tls_parameters(inner)
}
pub fn domain(&self) -> &str {
&self.domain
match &self.inner {
#[cfg(feature = "native-tls")]
InnerTlsParameters::NativeTls(inner) => &inner.server_name,
#[cfg(feature = "rustls")]
InnerTlsParameters::Rustls(inner) => inner.server_name.as_ref(),
#[cfg(feature = "boring-tls")]
InnerTlsParameters::BoringTls(inner) => &inner.server_name,
}
}
}
@@ -471,7 +484,7 @@ impl Certificate {
#[cfg(feature = "boring-tls")]
boring_tls: super::boring_tls::Certificate::from_der(&der)?,
#[cfg(feature = "rustls")]
rustls: vec![super::rustls::Certificate::from_der(der)?],
rustls: vec![super::rustls::Certificate::from_der(der)],
})
}

View File

@@ -11,9 +11,27 @@ pub(super) mod native_tls;
#[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> {
domain: String,
server_name: String,
cert_store: B::CertificateStore,
root_certs: Vec<B::Certificate>,
identity: Option<B::Identity>,
@@ -23,9 +41,9 @@ struct TlsParametersBuilder<B: TlsBackend> {
}
impl<B: TlsBackend> TlsParametersBuilder<B> {
fn new(domain: String) -> Self {
fn new(server_name: String) -> Self {
Self {
domain,
server_name,
cert_store: Default::default(),
root_certs: Vec::new(),
identity: None,
@@ -65,13 +83,17 @@ impl<B: TlsBackend> TlsParametersBuilder<B> {
self
}
fn build_legacy(self) -> Result<self::current::TlsParameters, Error> {
let domain = self.domain.clone();
let connector = B::__build_legacy_connector(self)?;
Ok(self::current::TlsParameters { connector, domain })
fn build(self) -> Result<TlsParameters<B>, Error> {
let (server_name, connector, extra_info) = B::__build_connector(self)?;
Ok(TlsParameters {
server_name,
connector,
extra_info,
})
}
}
#[allow(private_bounds)]
trait TlsBackend: private::SealedTlsBackend {
type CertificateStore: Default;
type Certificate;
@@ -79,55 +101,33 @@ trait TlsBackend: private::SealedTlsBackend {
type MinTlsVersion: Default;
#[doc(hidden)]
fn __build_connector(builder: TlsParametersBuilder<Self>) -> Result<Self::Connector, Error>;
fn __build_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<(Self::ServerName, Self::Connector, Self::ExtraInfo), Error>;
#[doc(hidden)]
#[allow(private_interfaces)]
fn __build_legacy_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<self::current::InnerTlsParameters, Error>;
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters;
}
#[cfg(feature = "boring-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
#[derive(Debug)]
#[allow(missing_copy_implementations)]
#[non_exhaustive]
pub(super) struct BoringTls;
#[cfg(feature = "native-tls")]
type DefaultTlsBackend = NativeTls;
#[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;
#[cfg(all(feature = "rustls", not(feature = "native-tls")))]
type DefaultTlsBackend = Rustls;
#[allow(private_interfaces)]
fn __build_connector(builder: TlsParametersBuilder<Self>) -> Result<Self::Connector, Error> {
self::boring_tls::build_connector(builder)
}
#[allow(private_interfaces)]
fn __build_legacy_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<self::current::InnerTlsParameters, Error> {
let accept_invalid_hostnames = builder.accept_invalid_hostnames;
Self::__build_connector(builder).map(|connector| {
self::current::InnerTlsParameters::BoringTls {
connector,
accept_invalid_hostnames,
}
})
}
}
#[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(super) struct NativeTls;
pub(in crate::transport::smtp) struct NativeTls;
#[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
@@ -137,17 +137,17 @@ impl TlsBackend for NativeTls {
type Identity = self::native_tls::Identity;
type MinTlsVersion = self::native_tls::MinTlsVersion;
#[allow(private_interfaces)]
fn __build_connector(builder: TlsParametersBuilder<Self>) -> Result<Self::Connector, Error> {
fn __build_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<(Self::ServerName, Self::Connector, Self::ExtraInfo), Error> {
self::native_tls::build_connector(builder)
.map(|(server_name, connector)| (server_name, connector, ()))
}
#[allow(private_interfaces)]
fn __build_legacy_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<self::current::InnerTlsParameters, Error> {
Self::__build_connector(builder)
.map(|connector| self::current::InnerTlsParameters::NativeTls { connector })
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
self::current::TlsParameters {
inner: self::current::InnerTlsParameters::NativeTls(inner),
}
}
}
@@ -156,7 +156,7 @@ impl TlsBackend for NativeTls {
#[derive(Debug)]
#[allow(missing_copy_implementations)]
#[non_exhaustive]
pub(super) struct Rustls;
pub(in crate::transport::smtp) struct Rustls;
#[cfg(feature = "rustls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
@@ -166,37 +166,90 @@ impl TlsBackend for Rustls {
type Identity = self::rustls::Identity;
type MinTlsVersion = self::rustls::MinTlsVersion;
#[allow(private_interfaces)]
fn __build_connector(builder: TlsParametersBuilder<Self>) -> Result<Self::Connector, Error> {
fn __build_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<(Self::ServerName, Self::Connector, Self::ExtraInfo), Error> {
self::rustls::build_connector(builder)
.map(|(server_name, connector)| (server_name, connector, ()))
}
#[allow(private_interfaces)]
fn __build_legacy_connector(
builder: TlsParametersBuilder<Self>,
) -> Result<self::current::InnerTlsParameters, Error> {
Self::__build_connector(builder)
.map(|config| self::current::InnerTlsParameters::Rustls { config })
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
self::current::TlsParameters {
inner: self::current::InnerTlsParameters::Rustls(inner),
}
}
}
mod private {
pub(super) trait SealedTlsBackend: Sized {
type Connector;
#[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<(Self::ServerName, Self::Connector, Self::ExtraInfo), Error> {
let accept_invalid_hostnames = builder.accept_invalid_hostnames;
self::boring_tls::build_connector(builder).map(|(server_name, connector)| {
(
server_name,
connector,
BoringTlsExtraInfo {
accept_invalid_hostnames,
},
)
})
}
#[cfg(feature = "boring-tls")]
impl SealedTlsBackend for super::BoringTls {
type Connector = boring::ssl::SslConnector;
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;
}
}

View File

@@ -6,7 +6,7 @@ use crate::transport::smtp::error::{self, Error};
pub(super) fn build_connector(
builder: super::TlsParametersBuilder<super::NativeTls>,
) -> Result<TlsConnector, Error> {
) -> Result<(Box<str>, TlsConnector), Error> {
let mut tls_builder = TlsConnector::builder();
match builder.cert_store {
@@ -32,7 +32,8 @@ pub(super) fn build_connector(
tls_builder.identity(identity.0);
}
tls_builder.build().map_err(error::tls)
let connector = tls_builder.build().map_err(error::tls)?;
Ok((builder.server_name.into_boxed_str(), connector))
}
#[derive(Debug, Clone, Default)]

View File

@@ -6,7 +6,7 @@ use std::{
use rustls::{
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
pki_types::{self, ServerName, UnixTime},
pki_types::{self, UnixTime},
server::ParsedCertificate,
ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme,
};
@@ -15,7 +15,7 @@ use crate::transport::smtp::error::{self, Error};
pub(super) fn build_connector(
builder: super::TlsParametersBuilder<super::Rustls>,
) -> Result<Arc<ClientConfig>, Error> {
) -> Result<(ServerName, Arc<ClientConfig>), Error> {
let just_version3 = &[&rustls::version::TLS13];
let supported_versions = match builder.min_tls_version {
MinTlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
@@ -74,7 +74,42 @@ pub(super) fn build_connector(
} else {
tls.with_no_client_auth()
};
Ok(Arc::new(tls))
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)]
@@ -122,8 +157,8 @@ impl Certificate {
.map_err(|_| error::tls("invalid certificate"))
}
pub(super) fn from_der(der: Vec<u8>) -> Result<Self, Error> {
Ok(Self(der.into()))
pub(super) fn from_der(der: Vec<u8>) -> Self {
Self(der.into())
}
}
@@ -193,7 +228,7 @@ impl ServerCertVerifier for InvalidCertsVerifier {
&self,
end_entity: &pki_types::CertificateDer<'_>,
intermediates: &[pki_types::CertificateDer<'_>],
server_name: &ServerName<'_>,
server_name: &pki_types::ServerName<'_>,
_ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {