wip
This commit is contained in:
@@ -1,7 +1,65 @@
|
||||
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<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(tls_builder.build())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum CertificateStore {
|
||||
#[default]
|
||||
System,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Certificate(pub(super) boring::x509::X509);
|
||||
|
||||
@@ -40,3 +98,13 @@ impl Debug for Identity {
|
||||
f.debug_struct("Identity").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum MinTlsVersion {
|
||||
Tlsv10,
|
||||
Tlsv11,
|
||||
#[default]
|
||||
Tlsv12,
|
||||
Tlsv13,
|
||||
}
|
||||
|
||||
@@ -174,7 +174,10 @@ pub enum NativeTlsCertificateStore {
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub enum RustlsCertificateStore {
|
||||
#[cfg(all(feature = "rustls", feature = "rustls-native-certs"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "rustls", feature = "rustls-native-certs"))))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(all(feature = "rustls", feature = "rustls-native-certs")))
|
||||
)]
|
||||
NativeCerts,
|
||||
#[cfg(all(feature = "rustls", feature = "webpki-roots"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "rustls", feature = "webpki-roots"))))]
|
||||
@@ -204,6 +207,7 @@ pub struct TlsParameters {
|
||||
|
||||
/// Builder for `TlsParameters`
|
||||
#[derive(Debug, Clone)]
|
||||
#[deprecated]
|
||||
pub struct TlsParametersBuilder {
|
||||
domain: String,
|
||||
#[allow(deprecated)]
|
||||
@@ -216,6 +220,7 @@ pub struct TlsParametersBuilder {
|
||||
min_tls_version: TlsVersion,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl TlsParametersBuilder {
|
||||
/// Creates a new builder for `TlsParameters`
|
||||
pub fn new(domain: String) -> Self {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::transport::smtp::Error;
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub mod boring_tls;
|
||||
@@ -9,9 +11,81 @@ pub mod native_tls;
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub mod rustls;
|
||||
|
||||
pub trait TlsBackend: private::Sealed {
|
||||
#[derive(Debug)]
|
||||
pub struct TlsParametersBuilder<B: TlsBackend> {
|
||||
domain: 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> {
|
||||
pub(super) fn new(domain: String) -> Self {
|
||||
Self {
|
||||
domain,
|
||||
cert_store: Default::default(),
|
||||
root_certs: Vec::new(),
|
||||
identity: None,
|
||||
accept_invalid_certs: false,
|
||||
accept_invalid_hostnames: false,
|
||||
min_tls_version: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn certificate_store(mut self, cert_store: B::CertificateStore) -> Self {
|
||||
self.cert_store = cert_store;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_root_certificate(mut self, cert: B::Certificate) -> Self {
|
||||
self.root_certs.push(cert);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn identify_with(mut self, identity: B::Identity) -> Self {
|
||||
self.identity = Some(identity);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_tls_version(mut self, min_tls_version: B::MinTlsVersion) -> Self {
|
||||
self.min_tls_version = min_tls_version;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
|
||||
self.accept_invalid_certs = accept_invalid_certs;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
|
||||
self.accept_invalid_hostnames = accept_invalid_hostnames;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build_legacy(self) -> Result<self::deprecated::TlsParameters, Error> {
|
||||
let domain = self.domain.clone();
|
||||
let connector = B::__build_legacy_connector(self)?;
|
||||
Ok(self::deprecated::TlsParameters { connector, domain })
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TlsBackend: private::SealedTlsBackend {
|
||||
type CertificateStore: Default;
|
||||
type Certificate;
|
||||
type Identity;
|
||||
type MinTlsVersion: Default;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn __build_connector(builder: TlsParametersBuilder<Self>) -> Result<Self::Connector, Error>;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(private_interfaces)]
|
||||
fn __build_legacy_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<self::deprecated::InnerTlsParameters, Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
@@ -22,8 +96,27 @@ pub 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::Connector, Error> {
|
||||
self::boring_tls::build_connector(builder)
|
||||
}
|
||||
|
||||
#[allow(private_interfaces)]
|
||||
fn __build_legacy_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<self::deprecated::InnerTlsParameters, Error> {
|
||||
let accept_invalid_hostnames = builder.accept_invalid_hostnames;
|
||||
Self::__build_connector(builder).map(|connector| {
|
||||
self::deprecated::InnerTlsParameters::BoringTls {
|
||||
connector,
|
||||
accept_invalid_hostnames,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
@@ -34,8 +127,22 @@ pub 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<Self::Connector, Error> {
|
||||
self::native_tls::build_connector(builder)
|
||||
}
|
||||
|
||||
#[allow(private_interfaces)]
|
||||
fn __build_legacy_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<self::deprecated::InnerTlsParameters, Error> {
|
||||
Self::__build_connector(builder)
|
||||
.map(|connector| self::deprecated::InnerTlsParameters::NativeTls { connector })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
@@ -46,20 +153,42 @@ pub 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<Self::Connector, Error> {
|
||||
self::rustls::build_connector(builder)
|
||||
}
|
||||
|
||||
#[allow(private_interfaces)]
|
||||
fn __build_legacy_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<self::deprecated::InnerTlsParameters, Error> {
|
||||
Self::__build_connector(builder)
|
||||
.map(|config| self::deprecated::InnerTlsParameters::Rustls { config })
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
// FIXME: this should be `pub(super)` but the `private_bounds` lint doesn't like it
|
||||
pub trait Sealed {}
|
||||
pub trait SealedTlsBackend: Sized {
|
||||
type Connector;
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
impl Sealed for super::BoringTls {}
|
||||
impl SealedTlsBackend for super::BoringTls {
|
||||
type Connector = boring::ssl::SslConnector;
|
||||
}
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
impl Sealed for super::NativeTls {}
|
||||
impl SealedTlsBackend for super::NativeTls {
|
||||
type Connector = native_tls::TlsConnector;
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
impl Sealed for super::Rustls {}
|
||||
impl SealedTlsBackend for super::Rustls {
|
||||
type Connector = std::sync::Arc<rustls::client::ClientConfig>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,48 @@
|
||||
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<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);
|
||||
}
|
||||
|
||||
tls_builder.build().map_err(error::tls)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum CertificateStore {
|
||||
#[default]
|
||||
System,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Certificate(pub(super) native_tls::Certificate);
|
||||
|
||||
@@ -41,3 +82,12 @@ impl Debug for Identity {
|
||||
f.debug_struct("Identity").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum MinTlsVersion {
|
||||
Tlsv10,
|
||||
Tlsv11,
|
||||
#[default]
|
||||
Tlsv12,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ use std::{
|
||||
};
|
||||
|
||||
use rustls::{
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
client::{
|
||||
danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
ClientConfig,
|
||||
},
|
||||
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
|
||||
pki_types::{self, ServerName, UnixTime},
|
||||
server::ParsedCertificate,
|
||||
@@ -13,6 +16,91 @@ use rustls::{
|
||||
|
||||
use crate::transport::smtp::error::{self, Error};
|
||||
|
||||
pub(super) fn build_connector(
|
||||
builder: super::TlsParametersBuilder<super::Rustls>,
|
||||
) -> Result<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();
|
||||
|
||||
match builder.cert_store {
|
||||
#[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) = 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 = if builder.accept_invalid_certs || builder.accept_invalid_hostnames {
|
||||
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))
|
||||
} else {
|
||||
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()
|
||||
};
|
||||
Ok(Arc::new(tls))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum CertificateStore {
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
|
||||
#[cfg_attr(feature = "rustls-native-certs", default)]
|
||||
NativeCerts,
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
|
||||
#[cfg_attr(
|
||||
all(feature = "webpki-roots", not(feature = "rustls-native-certs")),
|
||||
default
|
||||
)]
|
||||
WebpkiRoots,
|
||||
#[cfg_attr(
|
||||
all(not(feature = "webpki-roots"), not(feature = "rustls-native-certs")),
|
||||
default
|
||||
)]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Certificate(pub(super) pki_types::CertificateDer<'static>);
|
||||
|
||||
@@ -76,6 +164,14 @@ impl Debug for Identity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum MinTlsVersion {
|
||||
#[default]
|
||||
Tlsv12,
|
||||
Tlsv13,
|
||||
}
|
||||
|
||||
// FIXME: remove `pub(super)`
|
||||
#[derive(Debug)]
|
||||
pub(super) struct InvalidCertsVerifier {
|
||||
|
||||
Reference in New Issue
Block a user