From 63c5fcccfc4d28348b0afa2129ecbe508d625723 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 1 May 2025 20:22:23 +0200 Subject: [PATCH] Start moving types over --- src/transport/smtp/client/mod.rs | 2 +- src/transport/smtp/client/tls/boring_tls.rs | 41 +++++ src/transport/smtp/client/tls/deprecated.rs | 181 +++----------------- src/transport/smtp/client/tls/mod.rs | 9 +- src/transport/smtp/client/tls/native_tls.rs | 42 +++++ src/transport/smtp/client/tls/rustls.rs | 147 ++++++++++++++++ 6 files changed, 262 insertions(+), 160 deletions(-) diff --git a/src/transport/smtp/client/mod.rs b/src/transport/smtp/client/mod.rs index e245cf2..dd9485a 100644 --- a/src/transport/smtp/client/mod.rs +++ b/src/transport/smtp/client/mod.rs @@ -48,7 +48,7 @@ mod async_connection; mod async_net; mod connection; mod net; -mod tls; +pub mod tls; /// The codec used for transparency #[derive(Debug)] diff --git a/src/transport/smtp/client/tls/boring_tls.rs b/src/transport/smtp/client/tls/boring_tls.rs index 8b13789..5c2a03e 100644 --- a/src/transport/smtp/client/tls/boring_tls.rs +++ b/src/transport/smtp/client/tls/boring_tls.rs @@ -1 +1,42 @@ +use std::fmt::{self, Debug}; +use crate::transport::smtp::error::{self, Error}; + +#[derive(Clone)] +pub struct Certificate(pub(super) boring::x509::X509); + +impl Certificate { + pub fn from_pem(pem: &[u8]) -> Result { + Ok(Self(boring::x509::X509::from_pem(pem).map_err(error::tls)?)) + } + + pub fn from_der(der: &[u8]) -> Result { + 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 struct Identity { + pub(super) chain: boring::x509::X509, + pub(super) key: PKey, +} + +impl Identity { + pub fn from_pem(pem: &[u8], key: &[u8]) -> Result { + 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(Self { cert, key }) + } +} + +impl Debug for Identity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Identity").finish_non_exhaustive() + } +} diff --git a/src/transport/smtp/client/tls/deprecated.rs b/src/transport/smtp/client/tls/deprecated.rs index 464a717..98386ee 100644 --- a/src/transport/smtp/client/tls/deprecated.rs +++ b/src/transport/smtp/client/tls/deprecated.rs @@ -11,13 +11,7 @@ use boring::{ #[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 rustls::{ClientConfig, RootCertStore}; #[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))] use crate::transport::smtp::{error, Error}; @@ -372,7 +366,7 @@ impl TlsParametersBuilder { } } for cert in self.root_certs { - tls_builder.add_root_certificate(cert.native_tls); + tls_builder.add_root_certificate(cert.native_tls.0); } tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames); tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs); @@ -390,7 +384,7 @@ impl TlsParametersBuilder { tls_builder.min_protocol_version(Some(min_tls_version)); if let Some(identity) = self.identity { - tls_builder.identity(identity.native_tls); + tls_builder.identity(identity.native_tls.0); } let connector = tls_builder.build().map_err(error::tls)?; @@ -547,12 +541,12 @@ impl TlsParametersBuilder { } for cert in self.root_certs { for rustls_cert in cert.rustls { - root_cert_store.add(rustls_cert).map_err(error::tls)?; + root_cert_store.add(rustls_cert.0).map_err(error::tls)?; } } let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames { - let verifier = InvalidCertsVerifier { + let verifier = super::rustls::InvalidCertsVerifier { ignore_invalid_hostnames: self.accept_invalid_hostnames, ignore_invalid_certs: self.accept_invalid_certs, roots: root_cert_store, @@ -565,8 +559,7 @@ impl TlsParametersBuilder { }; 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) + tls.with_client_auth_cert(identity.rustls_tls.chain, identity.rustls_tls.key) .map_err(error::tls)? } else { tls.with_no_client_auth() @@ -644,55 +637,46 @@ impl TlsParameters { #[allow(missing_copy_implementations)] pub struct Certificate { #[cfg(feature = "native-tls")] - native_tls: native_tls::Certificate, + native_tls: super::native_tls::Certificate, #[cfg(feature = "rustls")] - rustls: Vec>, + rustls: Vec, #[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"))] impl Certificate { /// Create a `Certificate` from a DER encoded certificate pub fn from_der(der: Vec) -> Result { - #[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 { #[cfg(feature = "native-tls")] - native_tls: native_tls_cert, - #[cfg(feature = "rustls")] - rustls: vec![der.into()], + native_tls: super::native_tls::Certificate::from_der(&der)?, #[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 pub fn from_pem(pem: &[u8]) -> Result { - #[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 = { + use rustls::pki_types::{self, pem::PemObject as _, CertificateDer}; + CertificateDer::pem_slice_iter(pem) + .map(|cert| Ok(super::rustls::Certificate(cert?))) .collect::, pki_types::pem::Error>>() .map_err(|_| error::tls("invalid certificates"))? }; Ok(Self { #[cfg(feature = "native-tls")] - native_tls: native_tls_cert, + native_tls: super::native_tls::Certificate::from_pem(pem)?, #[cfg(feature = "rustls")] rustls: rustls_cert, #[cfg(feature = "boring-tls")] - boring_tls: boring_tls_cert, + boring_tls: super::boring_tls::Certificate::from_pem(pem)?, }) } } @@ -704,14 +688,15 @@ impl Debug for Certificate { } /// An identity that can be used with [`TlsParametersBuilder::identify_with`] +#[derive(Clone)] #[allow(missing_copy_implementations)] pub struct Identity { #[cfg(feature = "native-tls")] - native_tls: native_tls::Identity, + native_tls: super::native_tls::Identity, #[cfg(feature = "rustls")] - rustls_tls: (Vec>, PrivateKeyDer<'static>), + rustls_tls: super::rustls::Identity, #[cfg(feature = "boring-tls")] - boring_tls: (boring::x509::X509, PKey), + boring_tls: super::boring_tls::Identity, } impl Debug for Identity { @@ -720,132 +705,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"))] impl Identity { pub fn from_pem(pem: &[u8], key: &[u8]) -> Result { Ok(Self { #[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")] - rustls_tls: Identity::from_pem_rustls_tls(pem, key)?, + rustls_tls: super::rustls::Identity::from_pem(pem, key)?, #[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::from_pkcs8(pem, key).map_err(error::tls) - } - - #[cfg(feature = "rustls")] - fn from_pem_rustls_tls( - pem: &[u8], - key: &[u8], - ) -> Result<(Vec>, 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), 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, -} - -#[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 { - 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 { - verify_tls12_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.crypto_provider.signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.crypto_provider - .signature_verification_algorithms - .supported_schemes() - } } diff --git a/src/transport/smtp/client/tls/mod.rs b/src/transport/smtp/client/tls/mod.rs index a520474..2b31136 100644 --- a/src/transport/smtp/client/tls/mod.rs +++ b/src/transport/smtp/client/tls/mod.rs @@ -1,7 +1,10 @@ #[cfg(feature = "boring-tls")] -mod boring_tls; +#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))] +pub mod boring_tls; pub(super) mod deprecated; #[cfg(feature = "native-tls")] -mod native_tls; +#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] +pub mod native_tls; #[cfg(feature = "rustls")] -mod rustls; +#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] +pub mod rustls; diff --git a/src/transport/smtp/client/tls/native_tls.rs b/src/transport/smtp/client/tls/native_tls.rs index 8b13789..1341a1c 100644 --- a/src/transport/smtp/client/tls/native_tls.rs +++ b/src/transport/smtp/client/tls/native_tls.rs @@ -1 +1,43 @@ +use std::fmt::{self, Debug}; +use crate::transport::smtp::error::{self, Error}; + +#[derive(Clone)] +pub struct Certificate(pub(super) native_tls::Certificate); + +impl Certificate { + pub fn from_pem(pem: &[u8]) -> Result { + Ok(Self( + native_tls::Certificate::from_pem(pem).map_err(error::tls)?, + )) + } + + pub fn from_der(der: &[u8]) -> Result { + 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 struct Identity(pub(super) native_tls::Identity); + +impl Identity { + pub fn from_pem(pem: &[u8], key: &[u8]) -> Result { + 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() + } +} diff --git a/src/transport/smtp/client/tls/rustls.rs b/src/transport/smtp/client/tls/rustls.rs index 8b13789..80edb06 100644 --- a/src/transport/smtp/client/tls/rustls.rs +++ b/src/transport/smtp/client/tls/rustls.rs @@ -1 +1,148 @@ +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, ServerName, UnixTime}, + server::ParsedCertificate, + DigitallySignedStruct, RootCertStore, SignatureScheme, +}; + +use crate::transport::smtp::error::{self, Error}; + +#[derive(Clone)] +pub struct Certificate(pub(super) pki_types::CertificateDer<'static>); + +impl Certificate { + pub fn from_pem(pem: &[u8]) -> Result { + use rustls::pki_types::pem::PemObject as _; + + Ok(Self( + pki_types::CertificateDer::from_pem_slice(pem) + .map_err(|_| error::tls("invalid certificate"))?, + )) + } + + pub fn from_der(der: Vec) -> Result { + Ok(Self(der.into())) + } +} + +impl Debug for Certificate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Certificate").finish_non_exhaustive() + } +} + +pub struct Identity { + pub(super) chain: Vec>, + pub(super) key: pki_types::PrivateKeyDer<'static>, +} + +impl Identity { + pub fn from_pem(pem: &[u8], key: &[u8]) -> Result { + 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() + } +} + +// FIXME: remove `pub(super)` +#[derive(Debug)] +pub(super) struct InvalidCertsVerifier { + pub(super) ignore_invalid_hostnames: bool, + pub(super) ignore_invalid_certs: bool, + pub(super) roots: RootCertStore, + pub(super) crypto_provider: Arc, +} + +impl ServerCertVerifier for InvalidCertsVerifier { + fn verify_server_cert( + &self, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &ServerName<'_>, + _ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + 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 { + 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 { + verify_tls13_signature( + message, + cert, + dss, + &self.crypto_provider.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.crypto_provider + .signature_verification_algorithms + .supported_schemes() + } +}