use std::sync::LazyLock;
use rustls::crypto::CryptoProvider;
static DEFAULT_PROVIDER: LazyLock<CryptoProvider> = LazyLock::new(default_provider);
#[cfg(not(any(
feature = "crypto-ring",
feature = "crypto-aws-lc-rs",
feature = "crypto-openssl"
)))]
compile_error!(
"No crypto provider selected. Enable one of: `crypto-ring`, `crypto-aws-lc-rs`, or `crypto-openssl`."
);
#[cfg(feature = "fips")]
pub use rustls::crypto::aws_lc_rs::{
cipher_suite::{
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
},
default_provider,
sign::any_supported_type,
};
#[cfg(all(feature = "crypto-ring", not(feature = "fips")))]
pub use rustls::crypto::ring::{
cipher_suite::{
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
},
default_provider,
sign::any_supported_type,
};
#[cfg(all(
feature = "crypto-aws-lc-rs",
not(feature = "fips"),
not(feature = "crypto-ring")
))]
pub use rustls::crypto::aws_lc_rs::{
cipher_suite::{
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
},
default_provider,
sign::any_supported_type,
};
#[cfg(all(
feature = "crypto-openssl",
not(feature = "fips"),
not(feature = "crypto-ring"),
not(feature = "crypto-aws-lc-rs")
))]
pub use rustls_openssl::{
cipher_suite::{
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
},
default_provider,
};
#[cfg(all(
feature = "crypto-openssl",
not(feature = "fips"),
not(feature = "crypto-ring"),
not(feature = "crypto-aws-lc-rs")
))]
pub fn any_supported_type(
der: &rustls::pki_types::PrivateKeyDer<'_>,
) -> Result<std::sync::Arc<dyn rustls::sign::SigningKey>, rustls::Error> {
use rustls::crypto::KeyProvider;
rustls_openssl::KeyProvider.load_private_key(der.clone_key())
}
pub fn kx_group_by_name(name: &str) -> Option<&'static dyn rustls::crypto::SupportedKxGroup> {
debug_assert_provider_precedence();
let provider = &*DEFAULT_PROVIDER;
debug_assert!(
!provider.kx_groups.is_empty(),
"the active crypto provider must advertise at least one kx group"
);
let named_group = match name {
"x25519" | "X25519" => rustls::NamedGroup::X25519,
"secp256r1" | "P-256" => rustls::NamedGroup::secp256r1,
"secp384r1" | "P-384" => rustls::NamedGroup::secp384r1,
"X25519MLKEM768" => rustls::NamedGroup::X25519MLKEM768,
_ => return None,
};
let resolved = provider
.kx_groups
.iter()
.find(|g| g.name() == named_group)
.copied();
debug_assert!(
resolved.is_none_or(|g| g.name() == named_group),
"resolved kx group must match the requested named group"
);
resolved
}
pub fn cipher_suite_by_name(name: &str) -> Option<rustls::SupportedCipherSuite> {
debug_assert_provider_precedence();
debug_assert!(
!DEFAULT_PROVIDER.cipher_suites.is_empty(),
"the active crypto provider must advertise at least one cipher suite"
);
let candidate = match name {
"TLS13_AES_256_GCM_SHA384" => Some(TLS13_AES_256_GCM_SHA384),
"TLS13_AES_128_GCM_SHA256" => Some(TLS13_AES_128_GCM_SHA256),
"TLS13_CHACHA20_POLY1305_SHA256" => Some(TLS13_CHACHA20_POLY1305_SHA256),
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => Some(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384),
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => Some(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256),
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => {
Some(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
}
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" => Some(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384),
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" => Some(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256),
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => {
Some(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
}
_ => None,
}?;
let wanted = candidate.suite();
let resolved = DEFAULT_PROVIDER
.cipher_suites
.iter()
.find(|s| s.suite() == wanted)
.copied();
debug_assert!(
resolved.is_none_or(|s| s.suite() == wanted),
"resolved cipher suite must match the suite the name mapped to"
);
resolved
}
#[inline]
fn debug_assert_provider_precedence() {
debug_assert!(
cfg!(feature = "crypto-ring")
|| cfg!(feature = "crypto-aws-lc-rs")
|| cfg!(feature = "crypto-openssl"),
"at least one crypto provider feature must be enabled"
);
debug_assert!(
!cfg!(feature = "fips") || cfg!(feature = "crypto-aws-lc-rs"),
"the `fips` feature must imply `crypto-aws-lc-rs`"
);
debug_assert!(
!cfg!(all(
feature = "crypto-openssl",
not(feature = "fips"),
not(feature = "crypto-ring"),
not(feature = "crypto-aws-lc-rs")
)) || cfg!(feature = "crypto-openssl"),
"the openssl provider arm is only reachable with openssl enabled"
);
}
#[cfg(test)]
mod tests {
use super::*;
use rustls::NamedGroup;
#[test]
fn default_provider_has_tls13_cipher_suites() {
let provider = default_provider();
let names: Vec<_> = provider.cipher_suites.iter().map(|cs| cs.suite()).collect();
assert!(
names.contains(&rustls::CipherSuite::TLS13_AES_256_GCM_SHA384),
"provider must support TLS13_AES_256_GCM_SHA384"
);
assert!(
names.contains(&rustls::CipherSuite::TLS13_AES_128_GCM_SHA256),
"provider must support TLS13_AES_128_GCM_SHA256"
);
#[cfg(not(feature = "fips"))]
assert!(
names.contains(&rustls::CipherSuite::TLS13_CHACHA20_POLY1305_SHA256),
"provider must support TLS13_CHACHA20_POLY1305_SHA256"
);
}
#[test]
fn default_provider_has_tls12_cipher_suites() {
let provider = default_provider();
let names: Vec<_> = provider.cipher_suites.iter().map(|cs| cs.suite()).collect();
assert!(
names.contains(&rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384),
"provider must support TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
);
assert!(
names.contains(&rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384),
"provider must support TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
);
}
#[test]
fn default_provider_has_classical_kx_groups() {
let provider = default_provider();
let groups: Vec<_> = provider.kx_groups.iter().map(|g| g.name()).collect();
#[cfg(not(feature = "fips"))]
assert!(
groups.contains(&NamedGroup::X25519),
"provider must support X25519 key exchange"
);
assert!(
groups.contains(&NamedGroup::secp256r1),
"provider must support secp256r1 key exchange"
);
assert!(
groups.contains(&NamedGroup::secp384r1),
"provider must support secp384r1 key exchange"
);
}
#[cfg(all(feature = "crypto-aws-lc-rs", not(feature = "fips")))]
#[test]
fn aws_lc_rs_supports_post_quantum_kx() {
let provider = default_provider();
let groups: Vec<_> = provider.kx_groups.iter().map(|g| g.name()).collect();
assert!(
groups.contains(&NamedGroup::X25519MLKEM768),
"aws-lc-rs provider must support X25519MLKEM768 post-quantum key exchange"
);
}
#[cfg(all(feature = "crypto-aws-lc-rs", not(feature = "fips")))]
#[test]
fn aws_lc_rs_has_more_kx_groups_than_classical() {
let provider = default_provider();
assert!(
provider.kx_groups.len() > 3,
"aws-lc-rs should have more than 3 kx groups (has {}), including post-quantum",
provider.kx_groups.len()
);
}
#[cfg(feature = "fips")]
#[test]
fn fips_provider_has_nist_kx_groups() {
let provider = default_provider();
let groups: Vec<_> = provider.kx_groups.iter().map(|g| g.name()).collect();
assert!(
groups.contains(&NamedGroup::secp256r1),
"FIPS provider must support secp256r1"
);
assert!(
groups.contains(&NamedGroup::secp384r1),
"FIPS provider must support secp384r1"
);
}
#[cfg(feature = "fips")]
#[test]
fn fips_provider_has_aes_gcm_cipher_suites() {
let provider = default_provider();
let suites: Vec<_> = provider.cipher_suites.iter().map(|cs| cs.suite()).collect();
assert!(
suites.contains(&rustls::CipherSuite::TLS13_AES_256_GCM_SHA384),
"FIPS provider must support TLS13_AES_256_GCM_SHA384"
);
assert!(
suites.contains(&rustls::CipherSuite::TLS13_AES_128_GCM_SHA256),
"FIPS provider must support TLS13_AES_128_GCM_SHA256"
);
}
#[cfg(all(feature = "crypto-ring", not(feature = "fips")))]
#[test]
fn ring_has_no_mlkem() {
let provider = default_provider();
let groups: Vec<_> = provider.kx_groups.iter().map(|g| g.name()).collect();
assert!(
!groups.contains(&NamedGroup::X25519MLKEM768),
"ring provider should not advertise X25519MLKEM768"
);
}
#[cfg(all(
feature = "crypto-openssl",
not(feature = "fips"),
not(feature = "crypto-ring"),
not(feature = "crypto-aws-lc-rs")
))]
#[test]
fn openssl_pq_kx_depends_on_openssl_version() {
let provider = default_provider();
let groups: Vec<_> = provider.kx_groups.iter().map(|g| g.name()).collect();
let has_pq = groups.contains(&NamedGroup::X25519MLKEM768);
if has_pq {
println!("OpenSSL 3.5+ detected: X25519MLKEM768 is available");
} else {
println!("OpenSSL < 3.5: X25519MLKEM768 not available, classical groups only");
}
assert!(groups.contains(&NamedGroup::X25519));
assert!(groups.contains(&NamedGroup::secp256r1));
}
#[cfg(not(feature = "fips"))]
#[test]
fn kx_group_by_name_resolves_x25519() {
let group = kx_group_by_name("x25519").expect("x25519 should be supported");
assert_eq!(group.name(), NamedGroup::X25519);
}
#[cfg(not(feature = "fips"))]
#[test]
fn kx_group_by_name_resolves_x25519_uppercase() {
let group = kx_group_by_name("X25519").expect("X25519 should be supported");
assert_eq!(group.name(), NamedGroup::X25519);
}
#[cfg(feature = "fips")]
#[test]
fn kx_group_by_name_returns_none_for_x25519_in_fips() {
assert!(
kx_group_by_name("x25519").is_none(),
"X25519 is not FIPS-approved"
);
assert!(
kx_group_by_name("X25519").is_none(),
"X25519 is not FIPS-approved"
);
}
#[test]
fn kx_group_by_name_resolves_p256() {
let group = kx_group_by_name("P-256").expect("P-256 should be supported");
assert_eq!(group.name(), NamedGroup::secp256r1);
}
#[test]
fn kx_group_by_name_resolves_secp256r1() {
let group = kx_group_by_name("secp256r1").expect("secp256r1 should be supported");
assert_eq!(group.name(), NamedGroup::secp256r1);
}
#[test]
fn kx_group_by_name_resolves_p384() {
let group = kx_group_by_name("P-384").expect("P-384 should be supported");
assert_eq!(group.name(), NamedGroup::secp384r1);
}
#[test]
fn kx_group_by_name_returns_none_for_unknown() {
assert!(kx_group_by_name("P-521").is_none());
assert!(kx_group_by_name("unknown").is_none());
assert!(kx_group_by_name("").is_none());
}
#[cfg(all(feature = "crypto-aws-lc-rs", not(feature = "fips")))]
#[test]
fn kx_group_by_name_resolves_x25519mlkem768() {
let group = kx_group_by_name("X25519MLKEM768").expect("X25519MLKEM768 should be supported");
assert_eq!(group.name(), NamedGroup::X25519MLKEM768);
}
#[cfg(all(feature = "crypto-ring", not(feature = "fips")))]
#[test]
fn kx_group_by_name_returns_none_for_mlkem_on_ring() {
assert!(
kx_group_by_name("X25519MLKEM768").is_none(),
"ring does not support X25519MLKEM768"
);
}
#[test]
fn can_load_rsa_private_key() {
use rustls::pki_types::PrivateKeyDer;
use rustls::pki_types::pem::PemObject;
let key_pem = include_str!("../assets/key.pem");
let private_key =
PrivateKeyDer::from_pem_slice(key_pem.as_bytes()).expect("failed to parse PEM key");
any_supported_type(&private_key).expect("provider must be able to load RSA private key");
}
#[test]
fn can_build_server_config_with_tls13() {
use std::sync::Arc;
let provider = default_provider();
let config = rustls::ServerConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&rustls::version::TLS13])
.expect("failed to build TLS 1.3 config")
.with_no_client_auth()
.with_cert_resolver(Arc::new(crate::tls::MutexCertificateResolver::default()));
assert!(
!config.alpn_protocols.contains(&b"h2".to_vec()),
"default config should not have ALPN set"
);
}
#[test]
fn can_build_server_config_with_tls12_and_tls13() {
use std::sync::Arc;
let provider = default_provider();
rustls::ServerConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
.expect("failed to build TLS 1.2+1.3 config")
.with_no_client_auth()
.with_cert_resolver(Arc::new(crate::tls::MutexCertificateResolver::default()));
}
#[cfg(all(feature = "crypto-aws-lc-rs", not(feature = "fips")))]
#[test]
fn pq_kx_compatible_with_server_config() {
use std::sync::Arc;
let provider = default_provider();
let has_pq = provider
.kx_groups
.iter()
.any(|g| g.name() == NamedGroup::X25519MLKEM768);
assert!(has_pq, "X25519MLKEM768 must be in the provider");
let config = rustls::ServerConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&rustls::version::TLS13])
.expect("TLS 1.3 config with PQ kx should build successfully")
.with_no_client_auth()
.with_cert_resolver(Arc::new(crate::tls::MutexCertificateResolver::default()));
assert!(
!config.alpn_protocols.contains(&b"h2".to_vec()),
"default config should not have ALPN set"
);
}
#[test]
fn default_cipher_list_names_resolve_to_valid_suites() {
use sozu_command::config::DEFAULT_CIPHER_LIST;
let all_suites = [
("TLS13_AES_256_GCM_SHA384", TLS13_AES_256_GCM_SHA384.suite()),
("TLS13_AES_128_GCM_SHA256", TLS13_AES_128_GCM_SHA256.suite()),
(
"TLS13_CHACHA20_POLY1305_SHA256",
TLS13_CHACHA20_POLY1305_SHA256.suite(),
),
(
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.suite(),
),
(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.suite(),
),
(
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256.suite(),
),
(
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384.suite(),
),
(
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.suite(),
),
(
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.suite(),
),
];
for name in DEFAULT_CIPHER_LIST {
let found = all_suites.iter().any(|(n, _)| *n == name);
assert!(
found,
"DEFAULT_CIPHER_LIST entry {name:?} does not match any known cipher suite"
);
}
assert_eq!(
DEFAULT_CIPHER_LIST.len(),
all_suites.len(),
"DEFAULT_CIPHER_LIST length should match number of known suites"
);
}
#[test]
fn cipher_suite_by_name_resolves_tls13() {
assert!(cipher_suite_by_name("TLS13_AES_256_GCM_SHA384").is_some());
assert!(cipher_suite_by_name("TLS13_AES_128_GCM_SHA256").is_some());
#[cfg(not(feature = "fips"))]
assert!(cipher_suite_by_name("TLS13_CHACHA20_POLY1305_SHA256").is_some());
}
#[test]
fn cipher_suite_by_name_resolves_tls12() {
assert!(cipher_suite_by_name("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384").is_some());
assert!(cipher_suite_by_name("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384").is_some());
}
#[test]
fn cipher_suite_by_name_returns_none_for_unknown() {
assert!(cipher_suite_by_name("UNKNOWN_CIPHER").is_none());
assert!(cipher_suite_by_name("").is_none());
assert!(cipher_suite_by_name("TLS_AES_256_GCM_SHA384").is_none());
}
}