This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 23:43:19 +02:00
1 parent 70388578cb
commit 2b7c67d755
22 files changed
+369 -59

No files matched your search

+64 -1
View File
@@ -186,6 +186,7 @@ mod tests {
tls12: true,
tls13: false,
cipher_suites: Vec::new(),
post_quantum: false,
},
..Settings::default()
};
@@ -199,6 +200,7 @@ mod tests {
tls12: false,
tls13: false,
cipher_suites: vec![],
post_quantum: false,
}
.validate()
.is_err());
@@ -214,6 +216,7 @@ mod tests {
tls12: true,
tls13: false,
cipher_suites: vec![tls13.clone()],
post_quantum: false,
}
.validate()
.is_err());
@@ -223,6 +226,7 @@ mod tests {
tls12: false,
tls13: true,
cipher_suites: vec![tls12.clone()],
post_quantum: false,
}
.validate()
.is_err());
@@ -231,11 +235,13 @@ mod tests {
tls12: true,
tls13: false,
cipher_suites: vec![],
post_quantum: false,
};
assert!(ProtocolPolicy {
tls12: true,
tls13: false,
cipher_suites: vec![]
cipher_suites: vec![],
post_quantum: false,
}
.validate_narrower_than(&global)
.is_ok());
@@ -243,6 +249,63 @@ mod tests {
.validate_narrower_than(&global)
.is_err());
}
#[test]
fn pqc_is_opt_in_and_not_a_cipher_suite() {
let legacy: ProtocolPolicy = serde_json::from_value(serde_json::json!({
"tls12": true, "tls13": true, "cipher_suites": []
}))
.unwrap();
assert!(!legacy.post_quantum);
assert!(serde_json::to_value(&legacy)
.unwrap()
.get("post_quantum")
.is_none());
let mut pqc = legacy.clone();
pqc.post_quantum = true;
assert!(pqc.validate().is_ok());
let groups = pqc.key_exchange_groups();
assert_eq!(groups.first().map(String::as_str), Some("X25519MLKEM768"));
assert!(groups.iter().any(|name| name == "X25519"));
assert!(!legacy
.key_exchange_groups()
.contains(&"X25519MLKEM768".into()));
assert!(!pqc
.effective_cipher_suites()
.iter()
.any(|suite| suite.contains("MLKEM")));
assert!(provider_for(&pqc).unwrap().kx_groups.len() > 1);
assert!(serde_json::to_value(&pqc).unwrap()["post_quantum"] == true);
}
#[test]
fn pqc_policy_requires_tls13_and_global_permission() {
let legacy = ProtocolPolicy::default();
let mut pqc = legacy.clone();
pqc.post_quantum = true;
assert!(pqc.validate_narrower_than(&legacy).is_err());
assert!(legacy.validate_narrower_than(&pqc).is_ok());
pqc.tls13 = false;
assert!(pqc.validate().is_err());
pqc.tls13 = true;
pqc.cipher_suites = vec!["X25519MLKEM768".into()];
assert!(pqc.validate().is_err()); // KEX name is not a cipher suite
}
#[test]
fn pqc_builds_both_tcp_tls_and_quic_configs() {
let dir = tempfile::tempdir().unwrap();
initialize_default(dir.path()).unwrap();
let mut settings = Settings::default();
settings.policy.post_quantum = true;
let tls = compile(&settings, Some(dir.path())).unwrap().unwrap();
assert!(tls.alpn_protocols.contains(&b"h2".to_vec()));
let h3 = crate::http3::crypto(&tls, &settings.policy);
assert!(h3.is_ok(), "hybrid HTTP/3 TLS config must build");
let mut tls12_only = settings.policy.clone();
tls12_only.tls13 = false;
assert!(crate::http3::crypto(&tls, &tls12_only).is_err());
}
#[test]
fn administration_tls_supports_default_and_selected_certificate() {
let dir = tempfile::tempdir().unwrap();
+54 -5
View File
@@ -52,6 +52,12 @@ pub struct ProtocolPolicy {
pub tls12: bool,
pub tls13: bool,
pub cipher_suites: Vec<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub post_quantum: bool,
}
fn is_false(enabled: &bool) -> bool {
!*enabled
}
impl Default for ProtocolPolicy {
fn default() -> Self {
@@ -59,6 +65,7 @@ impl Default for ProtocolPolicy {
tls12: true,
tls13: true,
cipher_suites: Vec::new(),
post_quantum: false,
}
}
}
@@ -68,11 +75,15 @@ impl ProtocolPolicy {
self.tls12 || self.tls13,
"at least one TLS protocol version must be enabled"
);
ensure!(
!self.post_quantum || self.tls13,
"post-quantum key exchange requires TLS 1.3"
);
ensure!(
self.cipher_suites.len() <= 16,
"at most 16 TLS cipher suites may be selected"
);
let available = cipher_suite_names();
let available = cipher_suite_names_for(self.post_quantum);
let mut seen = HashSet::new();
for suite in &self.cipher_suites {
ensure!(
@@ -97,7 +108,7 @@ impl ProtocolPolicy {
}
pub fn effective_cipher_suites(&self) -> Vec<String> {
let selected = if self.cipher_suites.is_empty() {
cipher_suite_names()
cipher_suite_names_for(self.post_quantum)
} else {
self.cipher_suites.clone()
};
@@ -120,6 +131,10 @@ impl ProtocolPolicy {
pub fn validate_narrower_than(&self, global: &Self) -> Result<()> {
self.validate()?;
global.validate()?;
ensure!(
!self.post_quantum || global.post_quantum,
"per-vhost post-quantum key exchange must be enabled globally"
);
ensure!(
!self.tls12 || global.tls12,
"per-vhost TLS policy cannot enable TLS 1.2 when it is disabled globally"
@@ -144,17 +159,51 @@ impl ProtocolPolicy {
}
}
fn base_provider(post_quantum: bool) -> rustls::crypto::CryptoProvider {
if !post_quantum {
return rustls::crypto::ring::default_provider();
}
let mut provider = rustls::crypto::aws_lc_rs::default_provider();
let hybrid = rustls::crypto::aws_lc_rs::kx_group::X25519MLKEM768;
provider
.kx_groups
.retain(|group| group.name() != hybrid.name());
provider.kx_groups.insert(0, hybrid);
provider
}
pub fn cipher_suite_names() -> Vec<String> {
rustls::crypto::ring::default_provider()
base_provider(false)
.cipher_suites
.iter()
.map(|suite| format!("{:?}", suite.suite()))
.collect()
}
fn provider_for(policy: &ProtocolPolicy) -> Result<rustls::crypto::CryptoProvider> {
pub fn cipher_suite_names_for(post_quantum: bool) -> Vec<String> {
if !post_quantum {
return cipher_suite_names();
}
base_provider(post_quantum)
.cipher_suites
.iter()
.map(|suite| format!("{:?}", suite.suite()))
.collect()
}
impl ProtocolPolicy {
pub fn key_exchange_groups(&self) -> Vec<String> {
base_provider(self.post_quantum)
.kx_groups
.iter()
.map(|group| format!("{:?}", group.name()))
.collect()
}
}
pub(crate) fn provider_for(policy: &ProtocolPolicy) -> Result<rustls::crypto::CryptoProvider> {
policy.validate()?;
let mut provider = rustls::crypto::ring::default_provider();
let mut provider = base_provider(policy.post_quantum);
if !policy.cipher_suites.is_empty() {
provider.cipher_suites.retain(|suite| {
let name = format!("{:?}", suite.suite());