use std::sync::{Arc, LazyLock};
use h3x::dquic::{
cert::handy::ToCertificate,
client::{ClientQuicConfig, ServerCertVerifierChoice},
server::ServerQuicConfig,
};
use rustls::{
RootCertStore,
client::WebPkiServerVerifier,
server::{WebPkiClientVerifier, danger::ClientCertVerifier},
};
pub const DHTTP_ROOT_CA: &[u8] = crate::bootstrap::DHTTP_ROOT_CA;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClientIdentityPolicy {
Optional,
Required,
}
pub fn dhttp_root_cert_store() -> &'static Arc<RootCertStore> {
static STORE: LazyLock<Arc<RootCertStore>> = LazyLock::new(|| {
let mut store = RootCertStore::empty();
store.add_parsable_certificates(DHTTP_ROOT_CA.to_certificate());
Arc::new(store)
});
&STORE
}
pub fn dhttp_server_cert_verifier() -> Arc<WebPkiServerVerifier> {
static VERIFIER: LazyLock<Arc<WebPkiServerVerifier>> = LazyLock::new(|| {
WebPkiServerVerifier::builder(dhttp_root_cert_store().clone())
.build()
.expect("BUG: webpki server verifier built from fixed DHTTP roots")
});
VERIFIER.clone()
}
pub fn dhttp_client_cert_verifier(policy: ClientIdentityPolicy) -> Arc<dyn ClientCertVerifier> {
let builder = WebPkiClientVerifier::builder(dhttp_root_cert_store().clone());
match policy {
ClientIdentityPolicy::Optional => builder
.allow_unauthenticated()
.build()
.expect("BUG: webpki client verifier built from fixed DHTTP roots"),
ClientIdentityPolicy::Required => builder
.build()
.expect("BUG: webpki client verifier built from fixed DHTTP roots"),
}
}
pub fn default_client_quic_config() -> ClientQuicConfig {
static CONFIG: LazyLock<ClientQuicConfig> = LazyLock::new(|| ClientQuicConfig {
verifier: ServerCertVerifierChoice::WebPki(dhttp_server_cert_verifier()),
alpns: vec![b"h3".to_vec()],
..Default::default()
});
CONFIG.clone()
}
pub fn default_server_quic_config() -> ServerQuicConfig {
static CONFIG: LazyLock<ServerQuicConfig> = LazyLock::new(|| ServerQuicConfig {
alpns: vec![b"h3".to_vec()],
backlog: 1024,
client_cert_verifier: dhttp_client_cert_verifier(ClientIdentityPolicy::Optional),
..Default::default()
});
CONFIG.clone()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn public_root_ca_constant_is_valid_certificate_material() {
let mut store = RootCertStore::empty();
let (added, ignored) = store.add_parsable_certificates(DHTTP_ROOT_CA.to_certificate());
assert_eq!(added, 1);
assert_eq!(ignored, 0);
}
#[test]
fn dhttp_root_store_contains_embedded_root() {
assert!(!dhttp_root_cert_store().roots.is_empty());
}
#[test]
fn default_client_config_uses_dhttp_webpki_roots_and_h3_alpn() {
let config = default_client_quic_config();
assert!(matches!(
config.verifier,
ServerCertVerifierChoice::WebPki(_)
));
assert_eq!(config.alpns, vec![b"h3".to_vec()]);
}
#[test]
fn default_client_config_is_stable_across_calls() {
let a = default_client_quic_config();
let b = default_client_quic_config();
assert_eq!(a, b);
}
#[test]
fn default_server_config_uses_optional_dhttp_client_auth_and_h3_alpn() {
let config = default_server_quic_config();
assert_eq!(config.alpns, vec![b"h3".to_vec()]);
assert_eq!(config.backlog, 1024);
assert!(Arc::strong_count(&config.client_cert_verifier) >= 1);
}
#[test]
fn default_server_config_is_stable_across_calls() {
let a = default_server_quic_config();
let b = default_server_quic_config();
assert_eq!(a, b);
}
#[test]
fn required_client_identity_policy_builds_verifier() {
let verifier = dhttp_client_cert_verifier(ClientIdentityPolicy::Required);
assert!(Arc::strong_count(&verifier) >= 1);
}
}