From fa6191983ad801f35591b94c883b7c1e1702414e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Mon, 2 Sep 2024 16:26:46 +0100 Subject: [PATCH] feat(tls-peer-certificates): Provide peer certs (#976) Add a method that, when using a TLS toolkit that supports it, returns the entire certificate chain. This is useful, for example, when implementing DANE support which has directives that apply to the issuer and not just to the leaf certificate. Not all TLS toolkits support this, so the code will panic if the method is called when using a TLS toolkit that has no way to return these certificates. --- src/transport/smtp/client/async_connection.rs | 6 +++ src/transport/smtp/client/async_net.rs | 42 +++++++++++++++++++ src/transport/smtp/client/connection.rs | 6 +++ src/transport/smtp/client/net.rs | 26 ++++++++++++ 4 files changed, 80 insertions(+) diff --git a/src/transport/smtp/client/async_connection.rs b/src/transport/smtp/client/async_connection.rs index f265b95..cdbdec5 100644 --- a/src/transport/smtp/client/async_connection.rs +++ b/src/transport/smtp/client/async_connection.rs @@ -373,4 +373,10 @@ impl AsyncSmtpConnection { pub fn peer_certificate(&self) -> Result, Error> { self.stream.get_ref().peer_certificate() } + + /// All the X509 certificates of the chain (DER encoded) + #[cfg(any(feature = "rustls-tls", feature = "boring-tls"))] + pub fn certificate_chain(&self) -> Result>, Error> { + self.stream.get_ref().certificate_chain() + } } diff --git a/src/transport/smtp/client/async_net.rs b/src/transport/smtp/client/async_net.rs index f4eadb6..b9e89c5 100644 --- a/src/transport/smtp/client/async_net.rs +++ b/src/transport/smtp/client/async_net.rs @@ -431,6 +431,48 @@ impl AsyncNetworkStream { } } + pub fn certificate_chain(&self) -> Result>, Error> { + match &self.inner { + #[cfg(feature = "tokio1")] + InnerAsyncNetworkStream::Tokio1Tcp(_) => { + Err(error::client("Connection is not encrypted")) + } + #[cfg(feature = "tokio1-native-tls")] + InnerAsyncNetworkStream::Tokio1NativeTls(_) => panic!("Unsupported"), + #[cfg(feature = "tokio1-rustls-tls")] + InnerAsyncNetworkStream::Tokio1RustlsTls(stream) => Ok(stream + .get_ref() + .1 + .peer_certificates() + .unwrap() + .iter() + .map(|c| c.to_vec()) + .collect()), + #[cfg(feature = "tokio1-boring-tls")] + InnerAsyncNetworkStream::Tokio1BoringTls(stream) => Ok(stream + .ssl() + .peer_cert_chain() + .unwrap() + .iter() + .map(|c| c.to_der().map_err(error::tls)) + .collect::, _>>()?), + #[cfg(feature = "async-std1")] + InnerAsyncNetworkStream::AsyncStd1Tcp(_) => { + Err(error::client("Connection is not encrypted")) + } + #[cfg(feature = "async-std1-rustls-tls")] + InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream + .get_ref() + .1 + .peer_certificates() + .unwrap() + .iter() + .map(|c| c.to_vec()) + .collect()), + InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"), + } + } + pub fn peer_certificate(&self) -> Result, Error> { match &self.inner { #[cfg(feature = "tokio1")] diff --git a/src/transport/smtp/client/connection.rs b/src/transport/smtp/client/connection.rs index b3dc62f..0e6ebdb 100644 --- a/src/transport/smtp/client/connection.rs +++ b/src/transport/smtp/client/connection.rs @@ -307,4 +307,10 @@ impl SmtpConnection { pub fn peer_certificate(&self) -> Result, Error> { self.stream.get_ref().peer_certificate() } + + /// All the X509 certificates of the chain (DER encoded) + #[cfg(any(feature = "rustls-tls", feature = "boring-tls"))] + pub fn certificate_chain(&self) -> Result>, Error> { + self.stream.get_ref().certificate_chain() + } } diff --git a/src/transport/smtp/client/net.rs b/src/transport/smtp/client/net.rs index 992fab5..ac9f588 100644 --- a/src/transport/smtp/client/net.rs +++ b/src/transport/smtp/client/net.rs @@ -223,6 +223,32 @@ impl NetworkStream { } } + #[cfg(any(feature = "rustls-tls", feature = "boring-tls"))] + pub fn certificate_chain(&self) -> Result>, Error> { + match &self.inner { + InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")), + #[cfg(feature = "native-tls")] + InnerNetworkStream::NativeTls(_) => panic!("Unsupported"), + #[cfg(feature = "rustls-tls")] + InnerNetworkStream::RustlsTls(stream) => Ok(stream + .conn + .peer_certificates() + .unwrap() + .iter() + .map(|c| c.to_vec()) + .collect()), + #[cfg(feature = "boring-tls")] + InnerNetworkStream::BoringTls(stream) => Ok(stream + .ssl() + .peer_cert_chain() + .unwrap() + .iter() + .map(|c| c.to_der().map_err(error::tls)) + .collect::, _>>()?), + InnerNetworkStream::None => panic!("InnerNetworkStream::None must never be built"), + } + } + #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] pub fn peer_certificate(&self) -> Result, Error> { match &self.inner {