#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
use rustls::server::WebPkiClientVerifier;
use rustls::{ClientConfig, RootCertStore, ServerConfig, SupportedCipherSuite};
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use std::sync::Arc;
use crate::tls::{ClientVerificationMode, TlsError, TlsMode};
#[derive(Debug)]
pub struct Tls13Config {
pub mode: TlsMode,
pub use_pq_kx: bool,
pub enable_early_data: bool,
pub max_early_data_size: u32,
pub crypto_provider: Option<rustls::crypto::CryptoProvider>,
pub protocol_versions: Vec<&'static rustls::SupportedProtocolVersion>,
pub cipher_suites: Option<Vec<SupportedCipherSuite>>,
pub alpn_protocols: Vec<Vec<u8>>,
pub max_fragment_size: Option<usize>,
pub resumption: rustls::client::Resumption,
pub key_log: Option<Arc<dyn rustls::KeyLog>>,
pub client_cert_chain: Option<Vec<CertificateDer<'static>>>,
pub client_private_key: Option<PrivateKeyDer<'static>>,
pub client_verification: ClientVerificationMode,
pub client_ca_roots: Option<RootCertStore>,
}
impl Default for Tls13Config {
fn default() -> Self {
Self {
mode: TlsMode::default(),
use_pq_kx: true,
enable_early_data: false,
max_early_data_size: 0,
crypto_provider: None,
protocol_versions: vec![&rustls::version::TLS13],
cipher_suites: None,
alpn_protocols: vec![],
max_fragment_size: None,
resumption: rustls::client::Resumption::in_memory_sessions(32),
key_log: None,
client_cert_chain: None,
client_private_key: None,
client_verification: ClientVerificationMode::default(),
client_ca_roots: None,
}
}
}
impl Tls13Config {
#[must_use]
pub fn classic() -> Self {
Self { mode: TlsMode::Classic, use_pq_kx: false, ..Default::default() }
}
#[must_use]
pub fn hybrid() -> Self {
Self { mode: TlsMode::Hybrid, use_pq_kx: true, ..Default::default() }
}
#[must_use]
pub fn pq() -> Self {
Self { mode: TlsMode::Pq, use_pq_kx: true, ..Default::default() }
}
#[must_use]
pub fn with_early_data(mut self, max_size: u32) -> Self {
self.enable_early_data = true;
self.max_early_data_size = max_size;
self
}
#[must_use]
pub fn with_pq_kx(mut self, use_pq: bool) -> Self {
self.use_pq_kx = use_pq;
self
}
#[must_use]
pub fn with_crypto_provider(mut self, provider: rustls::crypto::CryptoProvider) -> Self {
self.crypto_provider = Some(provider);
self
}
#[must_use]
pub fn with_protocol_versions(
mut self,
versions: Vec<&'static rustls::SupportedProtocolVersion>,
) -> Self {
self.protocol_versions = versions;
self
}
#[must_use]
pub fn with_cipher_suites(mut self, suites: Vec<SupportedCipherSuite>) -> Self {
self.cipher_suites = Some(suites);
self
}
#[must_use]
pub fn with_alpn_protocols(mut self, protocols: Vec<&'static str>) -> Self {
self.alpn_protocols = protocols.into_iter().map(|p| p.as_bytes().to_vec()).collect();
self
}
#[must_use]
pub fn with_max_fragment_size(mut self, size: usize) -> Self {
self.max_fragment_size = Some(size);
self
}
#[must_use]
pub fn with_resumption(mut self, resumption: rustls::client::Resumption) -> Self {
self.resumption = resumption;
self
}
#[must_use]
pub fn with_key_log(mut self, key_log: Arc<dyn rustls::KeyLog>) -> Self {
self.key_log = Some(key_log);
self
}
#[must_use]
pub fn with_client_cert(
mut self,
cert_chain: Vec<CertificateDer<'static>>,
private_key: PrivateKeyDer<'static>,
) -> Self {
self.client_cert_chain = Some(cert_chain);
self.client_private_key = Some(private_key);
self
}
#[must_use]
pub fn with_client_verification(mut self, mode: ClientVerificationMode) -> Self {
self.client_verification = mode;
self
}
#[must_use]
pub fn with_client_ca_roots(mut self, roots: RootCertStore) -> Self {
self.client_ca_roots = Some(roots);
self
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HandshakeState {
Start,
ClientHelloSent,
ServerHelloReceived,
ServerHelloSent,
ServerFinishedSent,
ServerFinishedReceived,
ClientFinishedSent,
Complete,
}
#[derive(Debug, Clone)]
pub struct HandshakeStats {
pub duration_ms: u64,
pub round_trips: u32,
pub kex_time_ms: u64,
pub cert_time_ms: u64,
pub client_hello_size: usize,
pub server_hello_size: usize,
}
impl Default for HandshakeStats {
fn default() -> Self {
Self {
duration_ms: 0,
round_trips: 2,
kex_time_ms: 0,
cert_time_ms: 0,
client_hello_size: 0,
server_hello_size: 0,
}
}
}
fn load_system_root_certs(
root_store: &mut RootCertStore,
) -> Result<(), Box<dyn std::error::Error>> {
let cert_result = rustls_native_certs::load_native_certs();
for error in &cert_result.errors {
tracing::warn!("Error loading some native root certificates: {}", error);
}
let mut loaded_count = 0usize;
for cert in cert_result.certs {
if root_store.add(cert).is_ok() {
loaded_count = loaded_count.saturating_add(1);
}
}
tracing::info!("Loaded {} root certificates from system store", loaded_count);
if root_store.is_empty() {
tracing::error!("No root certificates could be loaded from the system");
return Err("No root certificates available - TLS certificate validation impossible".into());
}
Ok(())
}
pub fn create_client_config(config: &Tls13Config) -> Result<ClientConfig, TlsError> {
let mut root_store = RootCertStore::empty();
if let Err(e) = load_system_root_certs(&mut root_store) {
return Err(TlsError::Certificate {
message: format!("Failed to load system root certificates: {}", e),
subject: None,
issuer: None,
code: crate::tls::error::ErrorCode::CertificateParseError,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Retry {
max_attempts: 3,
backoff_ms: 1000,
}),
});
}
let crypto_provider = get_configured_crypto_provider(config)?;
let builder = ClientConfig::builder_with_provider(crypto_provider.into())
.with_protocol_versions(&config.protocol_versions)
.map_err(|e| TlsError::Config {
message: e.to_string(),
field: Some("protocol_versions".to_string()),
code: crate::tls::error::ErrorCode::InvalidProtocolVersion,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "protocol_versions".to_string(),
suggestion: "Use supported protocol versions (TLSv1.2, TLSv1.3)".to_string(),
}),
})?;
let builder_with_roots = builder.with_root_certificates(root_store);
let mut client_config = if let (Some(cert_chain), Some(private_key)) =
(&config.client_cert_chain, &config.client_private_key)
{
builder_with_roots
.with_client_auth_cert(cert_chain.clone(), private_key.clone_key())
.map_err(|e| TlsError::Certificate {
message: format!("Failed to configure client certificate: {}", e),
subject: None,
issuer: None,
code: crate::tls::error::ErrorCode::CertificateParseError,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "client_cert".to_string(),
suggestion: "Ensure certificate and key are valid and match".to_string(),
}),
})?
} else {
builder_with_roots.with_no_client_auth()
};
if let Some(suites) = &config.cipher_suites
&& !suites.is_empty()
{
return Err(TlsError::UnsupportedCipherSuite {
cipher_suite: "custom cipher suite selection".to_string(),
code: crate::tls::error::ErrorCode::UnsupportedOperation,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "cipher_suites".to_string(),
suggestion: "Remove the cipher_suites field from TLS configuration to use default secure cipher suites".to_string(),
}),
});
}
if !config.alpn_protocols.is_empty() {
client_config.alpn_protocols = config.alpn_protocols.clone();
}
client_config.resumption = config.resumption.clone();
if let Some(ref key_log) = config.key_log {
client_config.key_log = key_log.clone();
}
Ok(client_config)
}
pub fn create_server_config(
config: &Tls13Config,
cert_chain: Vec<CertificateDer<'static>>,
private_key: PrivateKeyDer<'static>,
) -> Result<ServerConfig, TlsError> {
let crypto_provider = get_configured_crypto_provider(config)?;
let builder = ServerConfig::builder_with_provider(crypto_provider.into())
.with_protocol_versions(&config.protocol_versions)
.map_err(|e| TlsError::Config {
message: e.to_string(),
field: Some("protocol_versions".to_string()),
code: crate::tls::error::ErrorCode::InvalidProtocolVersion,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "protocol_versions".to_string(),
suggestion: "Use supported protocol versions (TLSv1.2, TLSv1.3)".to_string(),
}),
})?;
let mut server_config = match config.client_verification {
ClientVerificationMode::None => {
builder.with_no_client_auth().with_single_cert(cert_chain, private_key)?
}
ClientVerificationMode::Optional | ClientVerificationMode::Required => {
let client_roots = config.client_ca_roots.clone().ok_or_else(|| TlsError::Config {
message: "Client CA certificates required for mTLS".to_string(),
field: Some("client_ca_certs".to_string()),
code: crate::tls::error::ErrorCode::MissingCertificate,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "client_ca_certs".to_string(),
suggestion: "Provide CA certificates for client verification".to_string(),
}),
})?;
let verifier_builder = WebPkiClientVerifier::builder(Arc::new(client_roots));
let verifier = if config.client_verification == ClientVerificationMode::Optional {
verifier_builder.allow_unauthenticated().build().map_err(|e| TlsError::Config {
message: format!("Failed to build client verifier: {}", e),
field: Some("client_verification".to_string()),
code: crate::tls::error::ErrorCode::InvalidConfig,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::NoRecovery),
})?
} else {
verifier_builder.build().map_err(|e| TlsError::Config {
message: format!("Failed to build client verifier: {}", e),
field: Some("client_verification".to_string()),
code: crate::tls::error::ErrorCode::InvalidConfig,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::NoRecovery),
})?
};
builder.with_client_cert_verifier(verifier).with_single_cert(cert_chain, private_key)?
}
};
if let Some(suites) = &config.cipher_suites
&& !suites.is_empty()
{
return Err(TlsError::UnsupportedCipherSuite {
cipher_suite: "custom cipher suite selection".to_string(),
code: crate::tls::error::ErrorCode::UnsupportedOperation,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "cipher_suites".to_string(),
suggestion: "Remove the cipher_suites field from TLS configuration to use default secure cipher suites".to_string(),
}),
});
}
if !config.alpn_protocols.is_empty() {
server_config.alpn_protocols = config.alpn_protocols.clone();
}
if config.enable_early_data {
server_config.max_early_data_size = config.max_early_data_size;
}
if let Some(ref key_log) = config.key_log {
server_config.key_log = key_log.clone();
}
Ok(server_config)
}
fn get_crypto_provider(mode: TlsMode) -> Result<rustls::crypto::CryptoProvider, TlsError> {
match mode {
TlsMode::Classic => {
Ok(rustls::crypto::aws_lc_rs::default_provider())
}
TlsMode::Hybrid | TlsMode::Pq => {
let mut provider = rustls::crypto::aws_lc_rs::default_provider();
provider.kx_groups.sort_by_key(|group| {
let name = format!("{:?}", group.name());
if name.contains("MLKEM") { 0 } else { 1 }
});
if provider.kx_groups.is_empty() {
return Err(TlsError::Config {
message: "AWS-LC provider lacks essential key exchange groups".to_string(),
field: Some("crypto_provider".to_string()),
code: crate::tls::error::ErrorCode::InvalidConfig,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "crypto_provider".to_string(),
suggestion: "Ensure AWS-LC supports X25519 and secp256r1".to_string(),
}),
});
}
Ok(provider)
}
}
}
fn get_configured_crypto_provider(
config: &Tls13Config,
) -> Result<rustls::crypto::CryptoProvider, TlsError> {
if let Some(ref provider) = config.crypto_provider {
return Ok(provider.clone());
}
get_crypto_provider(config.mode)
}
#[must_use]
pub fn get_cipher_suites(mode: TlsMode) -> Vec<SupportedCipherSuite> {
match mode {
TlsMode::Classic | TlsMode::Hybrid => {
vec![
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256,
]
}
TlsMode::Pq => {
vec![
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384,
]
}
}
}
#[must_use]
pub fn get_secure_cipher_suites() -> Vec<SupportedCipherSuite> {
vec![
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256,
]
}
pub fn validate_cipher_suites(suites: &[SupportedCipherSuite]) -> Result<(), TlsError> {
let allowed_suites = get_secure_cipher_suites();
for suite in suites {
if !allowed_suites.contains(suite) {
return Err(TlsError::Config {
message: format!("Insecure or deprecated cipher suite: {:?}", suite),
field: Some("cipher_suites".to_string()),
code: crate::tls::error::ErrorCode::InvalidConfig,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "cipher_suites".to_string(),
suggestion: "Use only TLS 1.3 cipher suites: AES-256-GCM, ChaCha20-Poly1305, AES-128-GCM".to_string(),
}),
});
}
}
Ok(())
}
pub fn verify_config(config: &Tls13Config) -> Result<(), TlsError> {
let _ = config.mode;
if config.enable_early_data && config.max_early_data_size == 0 {
return Err(TlsError::Config {
message: "max_early_data_size must be set when early_data is enabled".to_string(),
field: Some("max_early_data_size".to_string()),
code: crate::tls::error::ErrorCode::InvalidConfig,
context: Box::default(),
recovery: Box::new(crate::tls::error::RecoveryHint::Reconfigure {
field: "max_early_data_size".to_string(),
suggestion: "Set max_early_data_size to a positive value".to_string(),
}),
});
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
use super::*;
#[test]
fn test_tls13_config_default_sets_expected_fields_succeeds() {
let config = Tls13Config::default();
assert_eq!(config.mode, TlsMode::Hybrid);
assert!(config.use_pq_kx);
assert!(!config.enable_early_data);
assert!(config.crypto_provider.is_none());
assert_eq!(config.protocol_versions, vec![&rustls::version::TLS13]);
assert!(config.cipher_suites.is_none());
assert!(config.alpn_protocols.is_empty());
assert!(config.max_fragment_size.is_none());
assert_eq!(config.max_early_data_size, 0);
}
#[test]
fn test_tls13_config_classic_disables_pq_mode_succeeds() {
let config = Tls13Config::classic();
assert_eq!(config.mode, TlsMode::Classic);
assert!(!config.use_pq_kx);
}
#[test]
fn test_tls13_config_with_early_data_sets_early_data_flag_succeeds() {
let config = Tls13Config::hybrid().with_early_data(4096);
assert!(config.enable_early_data);
assert_eq!(config.max_early_data_size, 4096);
}
#[test]
fn test_cipher_suites_returns_nonempty_list_succeeds() {
let classic_suites = get_cipher_suites(TlsMode::Classic);
assert_eq!(classic_suites.len(), 3);
let hybrid_suites = get_cipher_suites(TlsMode::Hybrid);
assert_eq!(hybrid_suites.len(), 3);
let pq_suites = get_cipher_suites(TlsMode::Pq);
assert_eq!(pq_suites.len(), 2);
}
#[test]
fn test_tls13_config_with_alpn_sets_alpn_protocols_succeeds() {
let config = Tls13Config::hybrid().with_alpn_protocols(vec!["h2", "http/1.1"]);
assert_eq!(config.alpn_protocols.len(), 2);
assert_eq!(config.alpn_protocols[0], b"h2");
assert_eq!(config.alpn_protocols[1], b"http/1.1");
}
#[test]
fn test_tls13_config_with_cipher_suites_sets_suite_list_succeeds() {
let suites = vec![
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
];
let config = Tls13Config::hybrid().with_cipher_suites(suites);
assert!(config.cipher_suites.is_some());
assert_eq!(config.cipher_suites.as_ref().unwrap().len(), 2);
}
#[test]
fn test_tls13_config_with_max_fragment_size_sets_fragment_size_has_correct_size() {
let config = Tls13Config::hybrid().with_max_fragment_size(4096);
assert!(config.max_fragment_size.is_some());
assert_eq!(config.max_fragment_size.unwrap(), 4096);
}
#[test]
fn test_secure_cipher_suites_returns_nonempty_list_succeeds() {
let secure_suites = get_secure_cipher_suites();
assert_eq!(secure_suites.len(), 3);
assert!(validate_cipher_suites(&secure_suites).is_ok());
}
#[test]
fn test_verify_config_succeeds_for_valid_config_succeeds() {
let valid_config = Tls13Config::hybrid();
assert!(verify_config(&valid_config).is_ok());
let invalid_config = Tls13Config {
mode: TlsMode::Classic,
use_pq_kx: false,
enable_early_data: true,
max_early_data_size: 0,
crypto_provider: None,
protocol_versions: vec![&rustls::version::TLS13],
cipher_suites: None,
alpn_protocols: vec![],
max_fragment_size: None,
resumption: rustls::client::Resumption::in_memory_sessions(32),
key_log: None,
client_cert_chain: None,
client_private_key: None,
client_verification: ClientVerificationMode::None,
client_ca_roots: None,
};
assert!(verify_config(&invalid_config).is_err());
}
#[test]
fn test_handshake_stats_default_is_zero() {
let stats = HandshakeStats::default();
assert_eq!(stats.round_trips, 2);
assert_eq!(stats.duration_ms, 0);
}
#[test]
fn test_handshake_state_transitions_through_expected_states_succeeds() {
let state = HandshakeState::Start;
assert_eq!(state, HandshakeState::Start);
let complete = HandshakeState::Complete;
assert!(complete != state);
}
#[test]
fn test_crypto_provider_selection_returns_correct_provider_succeeds() {
let classic_provider = get_crypto_provider(TlsMode::Classic);
assert!(classic_provider.is_ok());
let hybrid_provider = get_crypto_provider(TlsMode::Hybrid);
assert!(hybrid_provider.is_ok());
let pq_provider = get_crypto_provider(TlsMode::Pq);
assert!(pq_provider.is_ok());
}
#[test]
fn test_configured_crypto_provider_returns_aws_lc_succeeds() {
let custom_provider = rustls::crypto::aws_lc_rs::default_provider();
let config = Tls13Config::hybrid().with_crypto_provider(custom_provider.clone());
let provider = get_configured_crypto_provider(&config);
assert!(provider.is_ok());
assert_eq!(provider.unwrap().kx_groups.len(), custom_provider.kx_groups.len());
}
#[test]
fn test_tls13_config_pq_enables_pq_mode_flag_succeeds() {
let config = Tls13Config::pq();
assert_eq!(config.mode, TlsMode::Pq);
assert!(config.use_pq_kx);
}
#[test]
fn test_tls13_config_with_pq_kx_sets_pq_kx_flag_succeeds() {
let config = Tls13Config::hybrid().with_pq_kx(false);
assert!(!config.use_pq_kx);
let config2 = Tls13Config::classic().with_pq_kx(true);
assert!(config2.use_pq_kx);
}
#[test]
fn test_tls13_config_with_protocol_versions_sets_version_range_succeeds() {
let config = Tls13Config::hybrid().with_protocol_versions(vec![&rustls::version::TLS13]);
assert_eq!(config.protocol_versions.len(), 1);
}
#[test]
fn test_tls13_config_with_resumption_sets_resumption_flag_succeeds() {
let resumption = rustls::client::Resumption::in_memory_sessions(64);
let _config = Tls13Config::hybrid().with_resumption(resumption);
}
#[test]
fn test_tls13_config_with_client_verification_sets_verification_mode_succeeds() {
let config =
Tls13Config::hybrid().with_client_verification(ClientVerificationMode::Required);
assert_eq!(config.client_verification, ClientVerificationMode::Required);
let config2 =
Tls13Config::hybrid().with_client_verification(ClientVerificationMode::Optional);
assert_eq!(config2.client_verification, ClientVerificationMode::Optional);
}
#[test]
fn test_tls13_config_with_client_ca_roots_sets_ca_roots_succeeds() {
let roots = RootCertStore::empty();
let config = Tls13Config::hybrid().with_client_ca_roots(roots);
assert!(config.client_ca_roots.is_some());
}
#[test]
fn test_tls13_config_with_client_cert_sets_client_cert_succeeds() {
let cert_bytes = vec![0u8; 32];
let cert = CertificateDer::from(cert_bytes);
let key = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(vec![1u8; 32]));
let config = Tls13Config::hybrid().with_client_cert(vec![cert], key);
assert!(config.client_cert_chain.is_some());
assert!(config.client_private_key.is_some());
}
#[test]
fn test_handshake_state_all_variants_are_distinct_are_unique() {
let states = [
HandshakeState::Start,
HandshakeState::ClientHelloSent,
HandshakeState::ServerHelloReceived,
HandshakeState::ServerHelloSent,
HandshakeState::ServerFinishedSent,
HandshakeState::ServerFinishedReceived,
HandshakeState::ClientFinishedSent,
HandshakeState::Complete,
];
for (i, s1) in states.iter().enumerate() {
for (j, s2) in states.iter().enumerate() {
if i == j {
assert_eq!(s1, s2);
} else {
assert_ne!(s1, s2);
}
}
}
}
#[test]
fn test_handshake_state_clone_copy_debug_work_correctly_succeeds() {
let state = HandshakeState::ClientHelloSent;
let cloned = state;
let copied = state;
assert_eq!(state, cloned);
assert_eq!(state, copied);
let debug = format!("{:?}", state);
assert!(debug.contains("ClientHelloSent"));
}
#[test]
fn test_handshake_stats_fields_set_correctly_succeeds() {
let stats = HandshakeStats {
duration_ms: 150,
kex_time_ms: 50,
cert_time_ms: 30,
client_hello_size: 512,
server_hello_size: 1024,
..Default::default()
};
assert_eq!(stats.duration_ms, 150);
assert_eq!(stats.kex_time_ms, 50);
assert_eq!(stats.cert_time_ms, 30);
assert_eq!(stats.client_hello_size, 512);
assert_eq!(stats.server_hello_size, 1024);
}
#[test]
fn test_handshake_stats_clone_debug_work_correctly_succeeds() {
let stats = HandshakeStats::default();
let stats2 = stats.clone();
assert_eq!(stats.round_trips, stats2.round_trips);
let debug = format!("{:?}", stats);
assert!(debug.contains("HandshakeStats"));
}
#[test]
fn test_verify_config_valid_early_data_succeeds() {
let config = Tls13Config::hybrid().with_early_data(16384);
assert!(verify_config(&config).is_ok());
}
#[test]
fn test_verify_config_all_modes_succeed_succeeds() {
assert!(verify_config(&Tls13Config::classic()).is_ok());
assert!(verify_config(&Tls13Config::hybrid()).is_ok());
assert!(verify_config(&Tls13Config::pq()).is_ok());
}
#[test]
fn test_validate_cipher_suites_valid_succeeds() {
let suites = vec![rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384];
assert!(validate_cipher_suites(&suites).is_ok());
}
#[test]
fn test_validate_cipher_suites_empty_fails() {
let suites: Vec<SupportedCipherSuite> = vec![];
assert!(validate_cipher_suites(&suites).is_ok());
}
#[test]
fn test_get_cipher_suites_all_modes_return_nonempty_lists_succeeds() {
assert_eq!(get_cipher_suites(TlsMode::Classic).len(), 3);
assert_eq!(get_cipher_suites(TlsMode::Hybrid).len(), 3);
assert_eq!(get_cipher_suites(TlsMode::Pq).len(), 2);
}
#[test]
fn test_create_client_config_hybrid_succeeds() {
let config = Tls13Config::hybrid();
let result = create_client_config(&config);
assert!(result.is_ok());
}
#[test]
fn test_create_client_config_classic_succeeds() {
let config = Tls13Config::classic();
let result = create_client_config(&config);
assert!(result.is_ok());
}
#[test]
fn test_create_client_config_pq_succeeds() {
let config = Tls13Config::pq();
let result = create_client_config(&config);
assert!(result.is_ok());
}
#[test]
fn test_create_client_config_with_alpn_succeeds() {
let config = Tls13Config::hybrid().with_alpn_protocols(vec!["h2", "http/1.1"]);
let result = create_client_config(&config);
assert!(result.is_ok());
let client_config = result.unwrap();
assert_eq!(client_config.alpn_protocols.len(), 2);
}
#[test]
fn test_create_client_config_with_cipher_suites_rejected_fails() {
let suites = vec![rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384];
let config = Tls13Config::hybrid().with_cipher_suites(suites);
let result = create_client_config(&config);
assert!(result.is_err(), "Custom cipher suites should be rejected");
}
#[test]
fn test_create_client_config_with_custom_provider_succeeds() {
let provider = rustls::crypto::aws_lc_rs::default_provider();
let config = Tls13Config::hybrid().with_crypto_provider(provider);
let result = create_client_config(&config);
assert!(result.is_ok());
}
#[test]
fn test_create_client_config_with_key_log_succeeds() {
let key_log = Arc::new(rustls::KeyLogFile::new());
let config = Tls13Config::hybrid().with_key_log(key_log);
let result = create_client_config(&config);
assert!(result.is_ok());
}
#[test]
fn test_create_server_config_with_alpn_succeeds() {
let cert_bytes = vec![0u8; 32];
let cert = CertificateDer::from(cert_bytes);
let key = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(vec![1u8; 32]));
let config = Tls13Config::hybrid().with_alpn_protocols(vec!["h2"]);
let _result = create_server_config(&config, vec![cert], key);
}
#[test]
fn test_create_server_config_with_early_data_succeeds() {
let cert_bytes = vec![0u8; 32];
let cert = CertificateDer::from(cert_bytes);
let key = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(vec![1u8; 32]));
let config = Tls13Config::hybrid().with_early_data(16384);
let _result = create_server_config(&config, vec![cert], key);
}
#[test]
fn test_create_server_config_mtls_missing_ca_roots_returns_error() {
let cert_bytes = vec![0u8; 32];
let cert = CertificateDer::from(cert_bytes);
let key = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(vec![1u8; 32]));
let config =
Tls13Config::hybrid().with_client_verification(ClientVerificationMode::Required);
let result = create_server_config(&config, vec![cert], key);
assert!(result.is_err());
}
#[test]
fn test_create_server_config_with_cipher_suites_rejected_fails() {
let cert_bytes = vec![0u8; 32];
let cert = CertificateDer::from(cert_bytes);
let key = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(vec![1u8; 32]));
let suites = vec![rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384];
let config = Tls13Config::hybrid().with_cipher_suites(suites);
let result = create_server_config(&config, vec![cert], key);
assert!(result.is_err(), "Custom cipher suites should be rejected");
}
#[test]
fn test_tls13_config_with_key_log_setter_sets_key_log_flag_succeeds() {
let key_log = Arc::new(rustls::KeyLogFile::new());
let config = Tls13Config::hybrid().with_key_log(key_log);
assert!(config.key_log.is_some());
}
#[test]
fn test_get_configured_crypto_provider_default_returns_aws_lc_succeeds() {
let config = Tls13Config::hybrid();
let provider = get_configured_crypto_provider(&config);
assert!(provider.is_ok());
}
#[test]
fn test_get_configured_crypto_provider_classic_returns_aws_lc_succeeds() {
let config = Tls13Config::classic();
let provider = get_configured_crypto_provider(&config);
assert!(provider.is_ok());
}
#[test]
fn test_validate_cipher_suites_all_three_valid_succeeds() {
let suites = get_secure_cipher_suites();
assert!(validate_cipher_suites(&suites).is_ok());
}
#[test]
fn test_validate_cipher_suites_single_valid_succeeds() {
let suites = vec![rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256];
assert!(validate_cipher_suites(&suites).is_ok());
}
#[test]
fn test_tls13_config_debug_produces_nonempty_string_succeeds() {
let config = Tls13Config::hybrid();
let debug = format!("{:?}", config);
assert!(debug.contains("Tls13Config"));
}
fn generate_self_signed_cert() -> (CertificateDer<'static>, PrivateKeyDer<'static>) {
let key_pair = rcgen::KeyPair::generate().unwrap();
let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
let cert_der = CertificateDer::from(cert.der().to_vec());
let key_der = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(
key_pair.serialize_der(),
));
(cert_der, key_der)
}
fn generate_ca_and_server_cert()
-> (CertificateDer<'static>, CertificateDer<'static>, PrivateKeyDer<'static>) {
let ca_key = rcgen::KeyPair::generate().unwrap();
let mut ca_params = rcgen::CertificateParams::new(vec!["Test CA".to_string()]).unwrap();
ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let ca_cert = ca_params.self_signed(&ca_key).unwrap();
let server_key = rcgen::KeyPair::generate().unwrap();
let server_params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
let server_cert = server_params.signed_by(&server_key, &ca_cert, &ca_key).unwrap();
let ca_cert_der = CertificateDer::from(ca_cert.der().to_vec());
let server_cert_der = CertificateDer::from(server_cert.der().to_vec());
let server_key_der = PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(
server_key.serialize_der(),
));
(ca_cert_der, server_cert_der, server_key_der)
}
#[test]
fn test_create_server_config_hybrid_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::hybrid();
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok(), "Server config with valid cert should succeed");
}
#[test]
fn test_create_server_config_classic_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::classic();
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok());
}
#[test]
fn test_create_server_config_pq_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::pq();
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok());
}
#[test]
fn test_create_server_config_with_alpn_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::hybrid().with_alpn_protocols(vec!["h2", "http/1.1"]);
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok());
let server_config = result.unwrap();
assert_eq!(server_config.alpn_protocols.len(), 2);
}
#[test]
fn test_create_server_config_with_early_data_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::hybrid().with_early_data(16384);
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok());
let server_config = result.unwrap();
assert_eq!(server_config.max_early_data_size, 16384);
}
#[test]
fn test_create_server_config_with_key_log_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let key_log = Arc::new(rustls::KeyLogFile::new());
let config = Tls13Config::hybrid().with_key_log(key_log);
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok());
}
#[test]
fn test_create_server_config_with_cipher_suites_valid_cert_rejected_fails() {
let (cert_der, key_der) = generate_self_signed_cert();
let suites = vec![rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384];
let config = Tls13Config::hybrid().with_cipher_suites(suites);
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_err(), "Custom cipher suites should be rejected even with valid cert");
}
#[test]
fn test_create_server_config_with_custom_provider_valid_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let provider = rustls::crypto::aws_lc_rs::default_provider();
let config = Tls13Config::classic().with_crypto_provider(provider);
let result = create_server_config(&config, vec![cert_der], key_der);
assert!(result.is_ok());
}
#[test]
fn test_create_server_config_mtls_required_succeeds() {
let (ca_cert_der, server_cert_der, server_key_der) = generate_ca_and_server_cert();
let mut ca_root_store = RootCertStore::empty();
ca_root_store.add(ca_cert_der).unwrap();
let config = Tls13Config::hybrid()
.with_client_verification(ClientVerificationMode::Required)
.with_client_ca_roots(ca_root_store);
let result = create_server_config(&config, vec![server_cert_der], server_key_der);
assert!(result.is_ok(), "mTLS Required with valid CA should succeed");
}
#[test]
fn test_create_server_config_mtls_optional_succeeds() {
let (ca_cert_der, server_cert_der, server_key_der) = generate_ca_and_server_cert();
let mut ca_root_store = RootCertStore::empty();
ca_root_store.add(ca_cert_der).unwrap();
let config = Tls13Config::hybrid()
.with_client_verification(ClientVerificationMode::Optional)
.with_client_ca_roots(ca_root_store);
let result = create_server_config(&config, vec![server_cert_der], server_key_der);
assert!(result.is_ok(), "mTLS Optional with valid CA should succeed");
}
#[test]
fn test_create_client_config_with_client_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::hybrid().with_client_cert(vec![cert_der], key_der);
let result = create_client_config(&config);
assert!(result.is_ok(), "Client config with client cert should succeed");
}
#[test]
fn test_create_client_config_classic_with_client_cert_succeeds() {
let (cert_der, key_der) = generate_self_signed_cert();
let config = Tls13Config::classic().with_client_cert(vec![cert_der], key_der);
let result = create_client_config(&config);
assert!(result.is_ok());
}
#[test]
fn test_create_server_config_chain_with_ca_succeeds() {
let (ca_cert_der, server_cert_der, server_key_der) = generate_ca_and_server_cert();
let chain = vec![server_cert_der, ca_cert_der];
let config = Tls13Config::hybrid();
let result = create_server_config(&config, chain, server_key_der);
assert!(result.is_ok(), "Server config with cert chain should succeed");
}
#[test]
fn test_tls13_config_pq_only_enables_pq_mode_flag_succeeds() {
let config = Tls13Config::pq();
assert!(config.use_pq_kx);
assert_eq!(config.mode, TlsMode::Pq);
}
#[test]
fn test_tls13_config_with_pq_kx_toggle_sets_flag_succeeds() {
let config = Tls13Config::hybrid().with_pq_kx(false);
assert!(!config.use_pq_kx);
let config2 = Tls13Config::classic().with_pq_kx(true);
assert!(config2.use_pq_kx);
}
#[test]
fn test_tls13_config_default_all_fields_are_expected_succeeds() {
let config = Tls13Config::default();
assert!(config.use_pq_kx);
assert!(!config.enable_early_data);
assert_eq!(config.max_early_data_size, 0);
assert!(config.alpn_protocols.is_empty());
assert!(config.max_fragment_size.is_none());
assert!(config.cipher_suites.is_none());
assert!(config.client_cert_chain.is_none());
assert!(config.client_private_key.is_none());
}
}