diff --git a/src/client/config.rs b/src/client/config.rs index 581676f4..3bfb792b 100644 --- a/src/client/config.rs +++ b/src/client/config.rs @@ -36,6 +36,7 @@ pub struct Config { #[derive(Clone, Debug)] pub(crate) enum TrustConfig { CaCertificateLocation(PathBuf), + CaCertificateBundle(Vec), TrustAll, Default, } @@ -127,12 +128,12 @@ impl Config { /// storage (or use `trust_cert_ca` instead), using this setting is potentially dangerous. /// /// # Panics - /// Will panic in case `trust_cert_ca` was called before. + /// Will panic in case `trust_cert_ca` or `trust_cert_ca_bundle` was called before. /// - /// - Defaults to `default`, meaning server certificate is validated against system-truststore. + /// - Defaults to validating the server certificate is validated against system's certificate storage. pub fn trust_cert(&mut self) { - if let TrustConfig::CaCertificateLocation(_) = &self.trust { - panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.") + if !matches!(&self.trust, TrustConfig::Default) { + panic!("'trust_cert'/'trust_cert_ca'/'trust_cert_ca_bundle' are mutual exclusive! Only use one.") } self.trust = TrustConfig::TrustAll; } @@ -143,14 +144,30 @@ impl Config { /// trust-chain. /// /// # Panics - /// Will panic in case `trust_cert` was called before. + /// Will panic in case `trust_cert` or `trust_cert_ca_bundle` was called before. /// /// - Defaults to validating the server certificate is validated against system's certificate storage. pub fn trust_cert_ca(&mut self, path: impl ToString) { - if let TrustConfig::TrustAll = &self.trust { - panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.") + if !matches!(&self.trust, TrustConfig::Default) { + panic!("'trust_cert'/'trust_cert_ca'/'trust_cert_ca_bundle' are mutual exclusive! Only use one.") + } else { + self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string())); + } + } + + /// If set, the server certificate will be validated against the given bundle of PEM-encoded CA certificates. + /// Useful when using self-signed certificates on the server without having to disable the + /// trust-chain. + /// + /// # Panics + /// Will panic in case `trust_cert` or `trust_cert_ca` was called before. + /// + /// - Defaults to validating the server certificate is validated against system's certificate storage. + pub fn trust_cert_ca_bundle(&mut self, bundle: Vec) { + if !matches!(&self.trust, TrustConfig::Default) { + panic!("'trust_cert'/'trust_cert_ca'/'trust_cert_ca_bundle' are mutual exclusive! Only use one.") } else { - self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string())) + self.trust = TrustConfig::CaCertificateBundle(bundle); } } diff --git a/src/client/tls_stream.rs b/src/client/tls_stream.rs index 9eba1060..2466a487 100644 --- a/src/client/tls_stream.rs +++ b/src/client/tls_stream.rs @@ -42,3 +42,30 @@ pub(crate) async fn create_tls_stream( ) -> crate::Result> { opentls_tls_stream::create_tls_stream(config, stream).await } + +#[cfg(any(feature = "native-tls", feature = "vendored-openssl"))] +mod iter_certs { + const BEGIN: &[u8] = b"-----BEGIN"; + pub struct IterCertBundle<'a> { + bundle: &'a [u8], + current_begin: Option, + } + impl<'a> IterCertBundle<'a> { + pub fn new(bundle: &'a [u8]) -> Self { + Self { + bundle, + current_begin: bundle.windows(BEGIN.len()).position(|x| x == BEGIN), + } + } + } + impl<'a> Iterator for IterCertBundle<'a> { + type Item = &'a [u8]; + fn next(&mut self) -> Option<&'a [u8]> { + self.current_begin.map(|begin| { + let next_begin = self.bundle.windows(BEGIN.len()).skip(begin + 1).position(|x| x == BEGIN).map(|x| x + begin + 1); + self.current_begin = next_begin; + &self.bundle[begin..next_begin.unwrap_or(self.bundle.len() - 1)] + }) + } + } +} diff --git a/src/client/tls_stream/native_tls_stream.rs b/src/client/tls_stream/native_tls_stream.rs index cf5591d8..750c7aa5 100644 --- a/src/client/tls_stream/native_tls_stream.rs +++ b/src/client/tls_stream/native_tls_stream.rs @@ -8,6 +8,8 @@ use futures_util::io::{AsyncRead, AsyncWrite}; use std::fs; use tracing::{event, Level}; +use super::iter_certs::IterCertBundle; + pub(crate) async fn create_tls_stream( config: &Config, stream: S, @@ -41,6 +43,11 @@ pub(crate) async fn create_tls_stream( }); } } + TrustConfig::CaCertificateBundle(bundle) => { + for cert in IterCertBundle::new(bundle) { + builder = builder.add_root_certificate(Certificate::from_pem(cert)?); + } + } TrustConfig::TrustAll => { event!( Level::WARN, diff --git a/src/client/tls_stream/opentls_tls_stream.rs b/src/client/tls_stream/opentls_tls_stream.rs index 1f028669..cbe59f51 100644 --- a/src/client/tls_stream/opentls_tls_stream.rs +++ b/src/client/tls_stream/opentls_tls_stream.rs @@ -8,6 +8,8 @@ use opentls::Certificate; use std::fs; use tracing::{event, Level}; +use super::iter_certs::IterCertBundle; + pub(crate) async fn create_tls_stream( config: &Config, stream: S, @@ -41,6 +43,11 @@ pub(crate) async fn create_tls_stream( }); } } + TrustConfig::CaCertificateBundle(bundle) => { + for cert in IterCertBundle::new(bundle) { + builder = builder.add_root_certificate(Certificate::from_pem(cert)?); + } + } TrustConfig::TrustAll => { event!( Level::WARN, diff --git a/src/client/tls_stream/rustls_tls_stream.rs b/src/client/tls_stream/rustls_tls_stream.rs index 0ccd0af9..b6a62434 100644 --- a/src/client/tls_stream/rustls_tls_stream.rs +++ b/src/client/tls_stream/rustls_tls_stream.rs @@ -115,6 +115,16 @@ impl TlsStream { }); } } + TrustConfig::CaCertificateBundle(bundle) => { + let mut cert_store = RootCertStore::empty(); + let certs = rustls_pemfile::certs(&mut bundle.as_slice())?; + for cert in certs { + cert_store.add(&Certificate(cert))?; + } + builder + .with_root_certificates(cert_store) + .with_no_client_auth() + } TrustConfig::TrustAll => { event!( Level::WARN,