From 2935cbd59b952e5131262f61f7d9f27482cbf99e Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 28 May 2024 13:41:55 +0900 Subject: [PATCH] wip: implemented try_into from the disseminated cert data to rustls server config --- rpxy-certs/Cargo.toml | 4 +- rpxy-certs/src/certs.rs | 5 +- rpxy-certs/src/crypto_source.rs | 2 +- rpxy-certs/src/error.rs | 3 + rpxy-certs/src/lib.rs | 8 +- rpxy-certs/src/server_crypto.rs | 134 ++++++++++++++++++++++++++++++-- 6 files changed, 140 insertions(+), 16 deletions(-) diff --git a/rpxy-certs/Cargo.toml b/rpxy-certs/Cargo.toml index 26febe4..732901d 100644 --- a/rpxy-certs/Cargo.toml +++ b/rpxy-certs/Cargo.toml @@ -11,16 +11,16 @@ edition.workspace = true publish.workspace = true [features] +default = ["http3"] +http3 = [] [dependencies] rustc-hash = { version = "1.1.0" } tracing = { version = "0.1.40" } -# anyhow = "1.0.86" derive_builder = { version = "0.20.0" } thiserror = { version = "1.0.61" } hot_reload = { version = "0.1.5" } async-trait = { version = "0.1.80" } -# tokio-rustls = { version = "0.26.0", features = ["early-data"] } rustls = { version = "0.23.8", default-features = false, features = [ "aws_lc_rs", ] } diff --git a/rpxy-certs/src/certs.rs b/rpxy-certs/src/certs.rs index 92578b3..8c8a2fb 100644 --- a/rpxy-certs/src/certs.rs +++ b/rpxy-certs/src/certs.rs @@ -74,8 +74,7 @@ impl SingleServerCertsKeys { /* ------------------------------------------------ */ /// Parse the client CA certificates and return a hashmap of pairs of a subject key identifier (key) and a trust anchor (value) - pub fn rustls_trust_anchors(&self) -> Result { - //-> Result<(Vec, HashSet>), anyhow::Error> { + pub fn rustls_client_certs_trust_anchors(&self) -> Result { let Some(certs) = self.client_ca_certs.as_ref() else { return Err(RpxyCertError::NoClientCert); }; @@ -149,7 +148,7 @@ mod tests { let certificed_key = crypto_elem.rustls_certified_key(); assert!(certificed_key.is_ok()); - let trust_anchors = crypto_elem.rustls_trust_anchors(); + let trust_anchors = crypto_elem.rustls_client_certs_trust_anchors(); assert!(trust_anchors.is_ok()); let trust_anchors = trust_anchors.unwrap(); diff --git a/rpxy-certs/src/crypto_source.rs b/rpxy-certs/src/crypto_source.rs index 4eae104..c204fbb 100644 --- a/rpxy-certs/src/crypto_source.rs +++ b/rpxy-certs/src/crypto_source.rs @@ -23,7 +23,7 @@ pub trait CryptoSource { /* ------------------------------------------------ */ #[derive(Builder, Debug, Clone)] -/// Crypto-related file reader implementing `CryptoSource`` trait +/// Crypto-related file reader implementing `CryptoSource` trait pub struct CryptoFileSource { #[builder(setter(custom))] /// Always exist diff --git a/rpxy-certs/src/error.rs b/rpxy-certs/src/error.rs index 34bd227..26e16b7 100644 --- a/rpxy-certs/src/error.rs +++ b/rpxy-certs/src/error.rs @@ -15,4 +15,7 @@ pub enum RpxyCertError { /// Error for hot reload certificate reloader #[error("Certificate reload error: {0}")] CertificateReloadError(#[from] hot_reload::ReloaderError), + /// Error when converting server name bytes to string + #[error("Failed to convert server name bytes to string: {0}")] + ServerNameBytesToString(#[from] std::string::FromUtf8Error), } diff --git a/rpxy-certs/src/lib.rs b/rpxy-certs/src/lib.rs index 2c616a2..b8abda8 100644 --- a/rpxy-certs/src/lib.rs +++ b/rpxy-certs/src/lib.rs @@ -9,15 +9,15 @@ mod log { pub(crate) use tracing::{debug, error, info, warn}; } /* ------------------------------------------------ */ +use crate::{error::*, reloader_service::CryptoReloader}; +use hot_reload::{ReloaderReceiver, ReloaderService}; + pub use crate::{ certs::SingleServerCertsKeys, crypto_source::{CryptoFileSource, CryptoFileSourceBuilder, CryptoFileSourceBuilderError, CryptoSource}, - server_crypto::{ServerCrypto, ServerNameBytes, ServerNameCryptoMap}, + server_crypto::{ServerCrypto, ServerCryptoBase}, }; -use crate::{error::*, reloader_service::CryptoReloader, server_crypto::ServerCryptoBase}; -use hot_reload::{ReloaderReceiver, ReloaderService}; - /* ------------------------------------------------ */ /// Constants TODO: define from outside const CERTS_WATCH_DELAY_SECS: u32 = 60; diff --git a/rpxy-certs/src/server_crypto.rs b/rpxy-certs/src/server_crypto.rs index 4494397..53fbcc3 100644 --- a/rpxy-certs/src/server_crypto.rs +++ b/rpxy-certs/src/server_crypto.rs @@ -1,19 +1,29 @@ -use crate::SingleServerCertsKeys; +use crate::{certs::SingleServerCertsKeys, error::*, log::*}; use rustc_hash::FxHashMap as HashMap; -use rustls::ServerConfig; +use rustls::{ + server::{ResolvesServerCertUsingSni, WebPkiClientVerifier}, + RootCertStore, ServerConfig, +}; use std::sync::Arc; /* ------------------------------------------------ */ /// ServerName in bytes type (TODO: this may be changed to define `common` layer defining types of names. or should be independent?) pub type ServerNameBytes = Vec; +/// Convert ServerName in bytes to string +fn server_name_bytes_to_string(server_name_bytes: &ServerNameBytes) -> Result { + let server_name = String::from_utf8(server_name_bytes.to_ascii_lowercase())?; + Ok(server_name) +} + /// ServerName (SNI) to ServerConfig map type pub type ServerNameCryptoMap = HashMap>; + /// ServerName (SNI) to ServerConfig map pub struct ServerCrypto { - // For Quic/HTTP3, only servers with no client authentication - pub inner_global_no_client_auth: Arc, - // // For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers - pub inner_local_map: Arc, + // For Quic/HTTP3, only servers with no client authentication, aggregated server config + pub aggregated_config_no_client_auth: Arc, + // For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers + pub individual_config_map: Arc, } /* ------------------------------------------------ */ @@ -23,3 +33,115 @@ pub struct ServerCryptoBase { /// Map of server name to certs and keys pub(super) inner: HashMap, } + +impl TryInto> for &ServerCryptoBase { + type Error = RpxyCertError; + + fn try_into(self) -> Result, Self::Error> { + let aggregated = self.build_aggrated_server_crypto()?; + let individual = self.build_individual_server_crypto_map()?; + + Ok(Arc::new(ServerCrypto { + aggregated_config_no_client_auth: Arc::new(aggregated), + individual_config_map: Arc::new(individual), + })) + } +} + +impl ServerCryptoBase { + /// Build individual server crypto inner object + fn build_individual_server_crypto_map(&self) -> Result { + let mut server_crypto_map: ServerNameCryptoMap = HashMap::default(); + + for (server_name_bytes, certs_keys) in self.inner.iter() { + let server_name = server_name_bytes_to_string(server_name_bytes)?; + + // Parse server certificates and private keys + let Ok(certified_key) = certs_keys.rustls_certified_key() else { + warn!("Failed to add certificate for {server_name}"); + continue; + }; + + let mut resolver_local = ResolvesServerCertUsingSni::new(); + if let Err(e) = resolver_local.add(&server_name, certified_key) { + error!("{server_name}: Failed to read some certificates and keys {e}"); + }; + + // With no client authentication case + if !certs_keys.is_mutual_tls() { + let mut server_crypto_local = ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(Arc::new(resolver_local)); + #[cfg(feature = "http3")] + { + server_crypto_local.alpn_protocols = vec![b"h3".to_vec(), b"h2".to_vec(), b"http/1.1".to_vec()]; + } + #[cfg(not(feature = "http3"))] + { + server_crypto_local.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + } + server_crypto_map.insert(server_name_bytes.clone(), Arc::new(server_crypto_local)); + continue; + } + + // With client authentication case, enable only http2 and http1.1 + let mut client_ca_roots_local = RootCertStore::empty(); + let Ok(trust_anchors) = certs_keys.rustls_client_certs_trust_anchors() else { + warn!("Failed to add client CA certificate for {server_name}"); + continue; + }; + let trust_anchors_without_skid = trust_anchors.values().map(|ta| ta.to_owned()); + client_ca_roots_local.extend(trust_anchors_without_skid); + + let Ok(client_cert_verifier) = WebPkiClientVerifier::builder(Arc::new(client_ca_roots_local)).build() else { + warn!("Failed to build client CA certificate verifier for {server_name}"); + continue; + }; + let mut server_crypto_local = ServerConfig::builder() + .with_client_cert_verifier(client_cert_verifier) + .with_cert_resolver(Arc::new(resolver_local)); + server_crypto_local.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + server_crypto_map.insert(server_name_bytes.clone(), Arc::new(server_crypto_local)); + } + + Ok(server_crypto_map) + } + + /* ------------------------------------------------ */ + /// Build aggregated server crypto inner object for no client auth server especially for http3 + fn build_aggrated_server_crypto(&self) -> Result { + let mut resolver_global = ResolvesServerCertUsingSni::new(); + + for (server_name_bytes, certs_keys) in self.inner.iter() { + let server_name = server_name_bytes_to_string(server_name_bytes)?; + + // Parse server certificates and private keys + let Ok(certified_key) = certs_keys.rustls_certified_key() else { + warn!("Failed to add certificate for {server_name}"); + continue; + }; + // Add server certificates and private keys to resolver only if client CA certs are not present + if !certs_keys.is_mutual_tls() { + // aggregated server config for no client auth server for http3 + if let Err(e) = resolver_global.add(&server_name, certified_key) { + error!("{server_name}: Failed to read some certificates and keys {e}"); + }; + } + } + + let mut server_crypto_global = ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(Arc::new(resolver_global)); + + #[cfg(feature = "http3")] + { + server_crypto_global.alpn_protocols = vec![b"h3".to_vec(), b"h2".to_vec(), b"http/1.1".to_vec()]; + } + #[cfg(not(feature = "http3"))] + { + server_crypto_global.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + } + + Ok(server_crypto_global) + } +}