//! Server seam round-trips: for each [`TlsIdentity`] variant, //! `TlsServerConfig::new` → `for_tcp_tls()` / `rustls_config()` (and //! `for_noq()` under the `noq` feature) all succeed, and the rustls config //! carries the expected ALPN list + `max_early_data_size` (the 0-RTT //! invariant, the server half). //! //! Handshakes are out of scope (the transport crates' job — the scope //! boundary, alktls ADR-001): these assert config construction at the //! public API surface only. use std::path::PathBuf; use alktls::{Ed25519SecretKey, TlsIdentity, TlsServerConfig}; #[cfg(not(feature = "acme"))] use alktls::AcmeDirectory; fn alpn() -> Vec> { vec![b"alk/call".to_vec(), b"alk/test".to_vec()] } fn write_x509_pem_pair(dir: &std::path::Path) -> (PathBuf, PathBuf) { let key_pair = rcgen::KeyPair::generate().expect("key gen"); let cert = rcgen::CertificateParams::default() .self_signed(&key_pair) .expect("self-signed cert"); let cert_path = dir.join("cert.pem"); let key_path = dir.join("key.pem"); std::fs::write(&cert_path, cert.pem()).expect("write cert pem"); std::fs::write(&key_path, key_pair.serialize_pem()).expect("write key pem"); (cert_path, key_path) } async fn build_config(identity: &TlsIdentity, alpns: &[Vec]) -> TlsServerConfig { TlsServerConfig::new(identity, alpns) .await .expect("server config must construct") } fn assert_rustls_config_carries_alpn_and_early_data(config: &TlsServerConfig, alpns: &[Vec]) { let rc = config.rustls_config(); assert_eq!( rc.alpn_protocols, alpns.to_vec(), "the rustls config must carry exactly the requested ALPN list" ); assert_eq!( rc.max_early_data_size, u32::MAX, "max_early_data_size must be u32::MAX on every server path (the 0-RTT invariant, ADR-001)" ); } #[tokio::test] async fn x509_identity_seam_round_trip() { let dir = tempfile::tempdir().expect("tempdir"); let (cert_path, key_path) = write_x509_pem_pair(dir.path()); let identity = TlsIdentity::X509 { cert: cert_path, key: key_path, }; let alpns = alpn(); let config = build_config(&identity, &alpns).await; assert_rustls_config_carries_alpn_and_early_data(&config, &alpns); #[cfg(feature = "tcp")] #[cfg(feature = "tcp")] let _acceptor = config.for_tcp_tls(); let _rc = config.rustls_config(); } #[tokio::test] async fn raw_key_identity_seam_round_trip() { let identity = TlsIdentity::RawKey(Ed25519SecretKey::generate()); let alpns = alpn(); let config = build_config(&identity, &alpns).await; assert_rustls_config_carries_alpn_and_early_data(&config, &alpns); #[cfg(feature = "tcp")] let _acceptor = config.for_tcp_tls(); let _rc = config.rustls_config(); } #[tokio::test] async fn self_signed_identity_seam_round_trip() { let identity = TlsIdentity::SelfSigned; let alpns = alpn(); let config = build_config(&identity, &alpns).await; assert_rustls_config_carries_alpn_and_early_data(&config, &alpns); #[cfg(feature = "tcp")] let _acceptor = config.for_tcp_tls(); let _rc = config.rustls_config(); } #[cfg(feature = "noq")] #[tokio::test] async fn raw_key_identity_for_noq_round_trip() { let identity = TlsIdentity::RawKey(Ed25519SecretKey::generate()); let alpns = alpn(); let config = build_config(&identity, &alpns).await; let noq_config = config .for_noq() .expect("for_noq must wrap the rustls config for QUIC"); let _ = noq_config; #[cfg(feature = "tcp")] let _acceptor = config.for_tcp_tls(); } #[cfg(not(feature = "acme"))] #[tokio::test] async fn acme_identity_is_a_config_error_without_the_feature() { let identity = TlsIdentity::Acme { domains: vec!["example.com".to_string()], cache_dir: tempfile::tempdir().expect("tempdir").path().join("cache"), directory: AcmeDirectory::Staging, contact: vec!["mailto:dev@example.com".to_string()], }; let result = TlsServerConfig::new(&identity, &alpn()).await; let err = match result { Ok(_) => panic!("Acme must not construct without the acme feature"), Err(e) => e, }; assert!( matches!(err, alktls::TlsError::AcmeConfig(_)), "Acme without the feature must surface TlsError::AcmeConfig, got {err:?}" ); }