From 132463a433f618694a2b33cb872ecf511150673e Mon Sep 17 00:00:00 2001 From: DarkCat09 Date: Tue, 10 Sep 2024 19:55:52 +0400 Subject: [PATCH] feat: add client cert resolver trait --- src/certs/mod.rs | 29 ++++++++++++++++++++++++++++- src/certs/resolver.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/client/builder.rs | 8 +++----- src/client/mod.rs | 31 ++++++++++++++++++++++++++++--- 4 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 src/certs/resolver.rs diff --git a/src/certs/mod.rs b/src/certs/mod.rs index af9b63d..2d41a0a 100644 --- a/src/certs/mod.rs +++ b/src/certs/mod.rs @@ -1,6 +1,7 @@ //! Everything related to TLS certs verification pub mod fingerprint; +pub mod resolver; pub mod verifier; #[cfg(feature = "file")] @@ -9,12 +10,18 @@ pub mod file; #[cfg(feature = "hickory")] pub mod dane; +use std::sync::Arc; + use async_trait::async_trait; +use tokio_rustls::rustls::{self, crypto::CryptoProvider}; + pub use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use resolver::InternalCertResolver; + /// Trait for implementing self-signed cert verifiers, /// probably via known_hosts with TOFU policy or DANE verification. -/// It is recommended to use helpers from file_sscv. +/// It is recommended to use helpers from [`file::sscv`] #[async_trait] pub trait SelfsignedCertVerifier: Send + Sync { async fn verify( @@ -33,3 +40,23 @@ pub struct SelfsignedCert { pub fingerprint: fingerprint::CertFingerprint, pub expires: u64, } + +/// Trait for implementing authentication cert resolvers, +/// choosing the right TLS client cert for the given host and path. +/// It is recommended to use helpers from [`file::accr`] +#[async_trait] +pub trait AuthCertResolver: Send + Sync { + async fn load(&self, host: &str, port: u16, path: &str) -> Option>; +} + +pub(crate) fn get_crypto_provider() -> Arc { + if let Some(provider) = CryptoProvider::get_default() { + provider.clone() + } else { + let provider = rustls::crypto::ring::default_provider(); + // unwrap: checked above that default not set + provider.install_default().unwrap(); + // unwrap: default has been just installed + CryptoProvider::get_default().unwrap().clone() + } +} diff --git a/src/certs/resolver.rs b/src/certs/resolver.rs new file mode 100644 index 0000000..30a1fe8 --- /dev/null +++ b/src/certs/resolver.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use tokio_rustls::rustls::{client::ResolvesClientCert, sign}; +use webpki::types::{CertificateDer, PrivateKeyDer}; + +use crate::LibError; + +#[derive(Debug)] +pub struct InternalCertResolver(Arc); + +impl InternalCertResolver { + pub fn new( + chain: Vec>, + key: PrivateKeyDer<'static>, + ) -> Result { + let provider = crate::certs::get_crypto_provider(); + + let private_key = provider.key_provider.load_private_key(key)?; + + Ok(InternalCertResolver(Arc::new(sign::CertifiedKey::new( + chain, + private_key, + )))) + } +} + +impl ResolvesClientCert for InternalCertResolver { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[tokio_rustls::rustls::SignatureScheme], + ) -> Option> { + Some(self.0.clone()) + } + + #[inline] + fn has_certs(&self) -> bool { + true + } +} diff --git a/src/client/builder.rs b/src/client/builder.rs index 97f9430..4168419 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -10,10 +10,7 @@ use crate::{ #[cfg(feature = "hickory")] use crate::dns::DnsClient; -use tokio_rustls::{ - rustls::{self, SupportedProtocolVersion}, - TlsConnector, -}; +use tokio_rustls::rustls::{self, SupportedProtocolVersion}; /// Builder for creating configured [`Client`] instance pub struct ClientBuilder { @@ -58,7 +55,8 @@ impl ClientBuilder { let tls_config = tls_config.with_no_client_auth(); Client { - connector: TlsConnector::from(Arc::new(tls_config)), + tls_config: Arc::new(tls_config), + cs_resolver: None, ss_verifier: self.ss_verifier, #[cfg(feature = "hickory")] dns: self.dns, diff --git a/src/client/mod.rs b/src/client/mod.rs index ef76f9e..2979eff 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -16,7 +16,7 @@ use hickory_client::rr::IntoName; use std::net::SocketAddr; use crate::{ - certs::{SelfsignedCertVerifier, ServerName}, + certs::{AuthCertResolver, SelfsignedCertVerifier, ServerName}, error::*, into_url::IntoUrl, status::*, @@ -34,7 +34,8 @@ use tokio_rustls::{client::TlsStream, rustls, TlsConnector}; pub type ThisResponse = Response>>; pub struct Client { - pub(crate) connector: TlsConnector, + pub(crate) tls_config: Arc, + pub(crate) cs_resolver: Option>, pub(crate) ss_verifier: Option>, #[cfg(feature = "hickory")] pub(crate) dns: Option, @@ -139,10 +140,34 @@ impl Client { .map_err(|_| InvalidUrl::ConvertError)? .to_owned(); + let connector = { + let cert = if let Some(cs_chooser) = &self.cs_resolver { + cs_chooser.load(host, port, "/").await // TODO path + } else { + None + }; + + let config = if let Some(cert) = cert { + // clone the underlying rustls::ClientConfig (not just copy Arc pointer) + let mut new_config = (*self.tls_config).clone(); + // `cert` is actually an instance of `certs::resolver::InternalCertResolver` + new_config.client_auth_cert_resolver = cert; + + Arc::new(new_config) + } else { + // Arc of config without client cert is stored in Client + // to avoid additional heap allocations on each request + // (new Arc-alloc only if auth cert is present, see the line above) + self.tls_config.clone() + }; + + TlsConnector::from(config) + }; + // TCP connection let stream = self.try_connect(host, port).await?; // TLS connection via tokio-rustls - let stream = self.connector.connect(domain, stream).await?; + let stream = connector.connect(domain, stream).await?; // certificate verification if let Some(ssv) = &self.ss_verifier {