wip: implemented try_into from the disseminated cert data to rustls server config
This commit is contained in:
parent
b168d1cdc5
commit
2935cbd59b
6 changed files with 140 additions and 16 deletions
|
|
@ -11,16 +11,16 @@ edition.workspace = true
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["http3"]
|
||||||
|
http3 = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustc-hash = { version = "1.1.0" }
|
rustc-hash = { version = "1.1.0" }
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
# anyhow = "1.0.86"
|
|
||||||
derive_builder = { version = "0.20.0" }
|
derive_builder = { version = "0.20.0" }
|
||||||
thiserror = { version = "1.0.61" }
|
thiserror = { version = "1.0.61" }
|
||||||
hot_reload = { version = "0.1.5" }
|
hot_reload = { version = "0.1.5" }
|
||||||
async-trait = { version = "0.1.80" }
|
async-trait = { version = "0.1.80" }
|
||||||
# tokio-rustls = { version = "0.26.0", features = ["early-data"] }
|
|
||||||
rustls = { version = "0.23.8", default-features = false, features = [
|
rustls = { version = "0.23.8", default-features = false, features = [
|
||||||
"aws_lc_rs",
|
"aws_lc_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)
|
/// 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<TrustAnchors, RpxyCertError> {
|
pub fn rustls_client_certs_trust_anchors(&self) -> Result<TrustAnchors, RpxyCertError> {
|
||||||
//-> Result<(Vec<OwnedTrustAnchor>, HashSet<Vec<u8>>), anyhow::Error> {
|
|
||||||
let Some(certs) = self.client_ca_certs.as_ref() else {
|
let Some(certs) = self.client_ca_certs.as_ref() else {
|
||||||
return Err(RpxyCertError::NoClientCert);
|
return Err(RpxyCertError::NoClientCert);
|
||||||
};
|
};
|
||||||
|
|
@ -149,7 +148,7 @@ mod tests {
|
||||||
let certificed_key = crypto_elem.rustls_certified_key();
|
let certificed_key = crypto_elem.rustls_certified_key();
|
||||||
assert!(certificed_key.is_ok());
|
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());
|
assert!(trust_anchors.is_ok());
|
||||||
|
|
||||||
let trust_anchors = trust_anchors.unwrap();
|
let trust_anchors = trust_anchors.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub trait CryptoSource {
|
||||||
|
|
||||||
/* ------------------------------------------------ */
|
/* ------------------------------------------------ */
|
||||||
#[derive(Builder, Debug, Clone)]
|
#[derive(Builder, Debug, Clone)]
|
||||||
/// Crypto-related file reader implementing `CryptoSource`` trait
|
/// Crypto-related file reader implementing `CryptoSource` trait
|
||||||
pub struct CryptoFileSource {
|
pub struct CryptoFileSource {
|
||||||
#[builder(setter(custom))]
|
#[builder(setter(custom))]
|
||||||
/// Always exist
|
/// Always exist
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,7 @@ pub enum RpxyCertError {
|
||||||
/// Error for hot reload certificate reloader
|
/// Error for hot reload certificate reloader
|
||||||
#[error("Certificate reload error: {0}")]
|
#[error("Certificate reload error: {0}")]
|
||||||
CertificateReloadError(#[from] hot_reload::ReloaderError<crate::server_crypto::ServerCryptoBase>),
|
CertificateReloadError(#[from] hot_reload::ReloaderError<crate::server_crypto::ServerCryptoBase>),
|
||||||
|
/// Error when converting server name bytes to string
|
||||||
|
#[error("Failed to convert server name bytes to string: {0}")]
|
||||||
|
ServerNameBytesToString(#[from] std::string::FromUtf8Error),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@ mod log {
|
||||||
pub(crate) use tracing::{debug, error, info, warn};
|
pub(crate) use tracing::{debug, error, info, warn};
|
||||||
}
|
}
|
||||||
/* ------------------------------------------------ */
|
/* ------------------------------------------------ */
|
||||||
|
use crate::{error::*, reloader_service::CryptoReloader};
|
||||||
|
use hot_reload::{ReloaderReceiver, ReloaderService};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
certs::SingleServerCertsKeys,
|
certs::SingleServerCertsKeys,
|
||||||
crypto_source::{CryptoFileSource, CryptoFileSourceBuilder, CryptoFileSourceBuilderError, CryptoSource},
|
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
|
/// Constants TODO: define from outside
|
||||||
const CERTS_WATCH_DELAY_SECS: u32 = 60;
|
const CERTS_WATCH_DELAY_SECS: u32 = 60;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
use crate::SingleServerCertsKeys;
|
use crate::{certs::SingleServerCertsKeys, error::*, log::*};
|
||||||
use rustc_hash::FxHashMap as HashMap;
|
use rustc_hash::FxHashMap as HashMap;
|
||||||
use rustls::ServerConfig;
|
use rustls::{
|
||||||
|
server::{ResolvesServerCertUsingSni, WebPkiClientVerifier},
|
||||||
|
RootCertStore, ServerConfig,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
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?)
|
/// 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<u8>;
|
pub type ServerNameBytes = Vec<u8>;
|
||||||
|
/// Convert ServerName in bytes to string
|
||||||
|
fn server_name_bytes_to_string(server_name_bytes: &ServerNameBytes) -> Result<String, RpxyCertError> {
|
||||||
|
let server_name = String::from_utf8(server_name_bytes.to_ascii_lowercase())?;
|
||||||
|
Ok(server_name)
|
||||||
|
}
|
||||||
|
|
||||||
/// ServerName (SNI) to ServerConfig map type
|
/// ServerName (SNI) to ServerConfig map type
|
||||||
pub type ServerNameCryptoMap = HashMap<ServerNameBytes, Arc<ServerConfig>>;
|
pub type ServerNameCryptoMap = HashMap<ServerNameBytes, Arc<ServerConfig>>;
|
||||||
|
|
||||||
/// ServerName (SNI) to ServerConfig map
|
/// ServerName (SNI) to ServerConfig map
|
||||||
pub struct ServerCrypto {
|
pub struct ServerCrypto {
|
||||||
// For Quic/HTTP3, only servers with no client authentication
|
// For Quic/HTTP3, only servers with no client authentication, aggregated server config
|
||||||
pub inner_global_no_client_auth: Arc<ServerConfig>,
|
pub aggregated_config_no_client_auth: Arc<ServerConfig>,
|
||||||
// // For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers
|
// For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers
|
||||||
pub inner_local_map: Arc<ServerNameCryptoMap>,
|
pub individual_config_map: Arc<ServerNameCryptoMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------ */
|
/* ------------------------------------------------ */
|
||||||
|
|
@ -23,3 +33,115 @@ pub struct ServerCryptoBase {
|
||||||
/// Map of server name to certs and keys
|
/// Map of server name to certs and keys
|
||||||
pub(super) inner: HashMap<ServerNameBytes, SingleServerCertsKeys>,
|
pub(super) inner: HashMap<ServerNameBytes, SingleServerCertsKeys>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryInto<Arc<ServerCrypto>> for &ServerCryptoBase {
|
||||||
|
type Error = RpxyCertError;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<Arc<ServerCrypto>, 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<ServerNameCryptoMap, RpxyCertError> {
|
||||||
|
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<ServerConfig, RpxyCertError> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue