generation 4: integration suite — seam round-trips + invariant pins
tests/server_seams.rs: - TlsServerConfig::new per identity variant → rustls_config() + for_tcp_tls (tcp) + for_noq (noq); ALPN + max_early_data_size=u32::MAX asserted per path; Acme-no-feature → AcmeConfig cell tests/client_seams.rs: - TlsClientConfig::new per credentials cell → into_rustls_config + for_noq; enable_early_data=true + single-value ALPN pinned tests/invariant_pins.rs: - nine-scheme exact-list pin (vec equality, order included) - verifier-selection + client-auth presentation matrices via public API (Debug probe for the pub(super) verifier, direct resolver introspection); root-store fallback non-empty tests/acme_lifecycle.rs (#![cfg(feature = acme)]): - spawn-and-return, acme-tls/1 in ALPN, resolver wiring, 0-RTT on the ACME branch; blackhole + staging URLs — zero network I/O Verification: all five feature combos green (default 68+13, noq 72+16, tcp 71+13, acme 68+11, all-features 75+17), clippy -D warnings, fmt --check, doc --no-deps
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
//! 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<u8>> {
|
||||
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<u8>]) -> TlsServerConfig {
|
||||
TlsServerConfig::new(identity, alpns)
|
||||
.await
|
||||
.expect("server config must construct")
|
||||
}
|
||||
|
||||
fn assert_rustls_config_carries_alpn_and_early_data(config: &TlsServerConfig, alpns: &[Vec<u8>]) {
|
||||
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:?}"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user