Files
alktls/tests/invariant_pins.rs
T
glm-5.3-flash 87b69e19e6 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
2026-09-10 15:00:06 +00:00

191 lines
7.6 KiB
Rust

//! The invariant pins, promoted to integration level (ADR-006: the
//! regression-proof shapes):
//!
//! 1. The nine-scheme exact-list pin — exact vec equality, not membership
//! (the shape the in-module seed test must not regress from).
//! 2. The verifier-selection matrix + client-auth presentation matrix,
//! exercised through the public API only.
//! 3. The root-store fallback — the store is never empty.
//! 4. The server `max_early_data_size` + request-but-don't-require shape.
//!
//! Fail-closed for unknown raw-key remotes is structural (the CA verifier
//! is what `None` installs; a raw-key remote cannot satisfy it — the
//! failure manifests at handshake, never via `TlsError`): the matrix
//! asserts `None` installs `WebPkiServerVerifier`, which is the
//! fail-closed structure. The verifier inside `rustls::ClientConfig` /
//! `rustls::ServerConfig` is `pub(super)` at rustls 0.23.44, so the
//! verifier identity is probed through the config's derived Debug output
//! (the verifier's type name appears in it) and the helpers' own
//! construction results.
use alktls::{
build_client_auth, build_rustls_server_config, load_platform_root_cert_store,
select_server_verifier, AcceptAnyCertVerifier, ConnectionCredentials, Ed25519SecretKey,
RemoteIdentity, TlsClientConfig, TlsIdentity,
};
fn provider() -> std::sync::Arc<rustls::crypto::CryptoProvider> {
std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider())
}
#[test]
fn nine_schemes_exact_list_pin() {
use rustls::server::danger::ClientCertVerifier;
let schemes = AcceptAnyCertVerifier.supported_verify_schemes();
assert_eq!(
schemes,
vec![
rustls::SignatureScheme::ED25519,
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
],
"the nine-scheme list is load-bearing (server.md) — exact equality, order included"
);
}
#[test]
fn verifier_selection_matrix_full_construction() {
let known_peer = ConnectionCredentials::new()
.with_local_identity(TlsIdentity::RawKey(Ed25519SecretKey::generate()))
.with_remote_identity(RemoteIdentity {
fingerprint: "ed25519:aa".to_string(),
});
let config = TlsClientConfig::new(&known_peer, b"alk/call")
.expect("Some(remote_identity) cell must construct");
assert!(
format!("{:?}", config.into_rustls_config()).contains("FingerprintPinVerifier"),
"Some(fingerprint) must install FingerprintPinVerifier"
);
let unknown_x509 = ConnectionCredentials::new();
let config = TlsClientConfig::new(&unknown_x509, b"alk/call")
.expect("None(remote_identity) cell must construct (the public-X.509 state)");
assert!(
format!("{:?}", config.into_rustls_config()).contains("WebPkiServerVerifier"),
"None must install WebPkiServerVerifier — the CA verifier. Fail-closed is structural: \
an unknown raw-key remote cannot satisfy it and fails at handshake, never via TlsError"
);
}
#[test]
fn client_auth_presentation_matrix_full_construction() {
let dir = tempfile::tempdir().expect("tempdir");
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.path().join("cert.pem");
let key_path = dir.path().join("key.pem");
std::fs::write(&cert_path, cert.pem()).expect("write cert");
std::fs::write(&key_path, key_pair.serialize_pem()).expect("write key");
let cells: Vec<(ConnectionCredentials, &str)> = vec![
(
ConnectionCredentials::new()
.with_local_identity(TlsIdentity::RawKey(Ed25519SecretKey::generate())),
"raw-key",
),
(
ConnectionCredentials::new().with_local_identity(TlsIdentity::X509 {
cert: cert_path,
key: key_path,
}),
"x509",
),
(
ConnectionCredentials::new().with_local_identity(TlsIdentity::SelfSigned),
"self-signed",
),
(ConnectionCredentials::new(), "none"),
];
for (credentials, label) in &cells {
let config = TlsClientConfig::new(credentials, b"alk/call")
.unwrap_or_else(|e| panic!("{label} cell must construct: {e}"));
let resolver = &config.into_rustls_config().client_auth_cert_resolver;
match *label {
"raw-key" => assert!(
resolver.has_certs() && resolver.only_raw_public_keys(),
"RawKey must present an RFC 7250 raw public key"
),
"x509" => assert!(
resolver.has_certs() && !resolver.only_raw_public_keys(),
"X509 must present the loaded chain (not raw public keys)"
),
_ => assert!(
!resolver.has_certs(),
"{label} presents nothing (NoClientCertResolver)"
),
}
}
let acme = ConnectionCredentials::new().with_local_identity(TlsIdentity::Acme {
domains: vec!["example.com".to_string()],
cache_dir: dir.path().to_path_buf(),
directory: alktls::AcmeDirectory::Production,
contact: vec![],
});
match TlsClientConfig::new(&acme, b"alk/call") {
Ok(_) => panic!("Acme is a server-only identity — client path must be a config error"),
Err(e) => assert!(
matches!(e, alktls::TlsError::AcmeConfig(_)),
"Acme on the client path must surface TlsError::AcmeConfig, got {e:?}"
),
}
}
#[test]
fn root_store_fallback_is_never_empty() {
let roots = load_platform_root_cert_store().expect("root store must load");
assert!(
!roots.is_empty(),
"the webpki-roots fallback guarantees a non-empty store even when the platform store is \
empty (alknet ADR-088 §5): got {} anchors",
roots.roots.len()
);
}
#[test]
fn selection_and_auth_helpers_agree_with_the_full_config() {
let sk = Ed25519SecretKey::generate();
let local = Some(TlsIdentity::RawKey(sk.clone()));
let resolver = build_client_auth(&provider(), &local).expect("raw-key resolver builds");
assert!(resolver.has_certs() && resolver.only_raw_public_keys());
let remote = Some(RemoteIdentity {
fingerprint: "ed25519:aa".to_string(),
});
let verifier = select_server_verifier(&provider(), &remote).expect("pin verifier selects");
assert!(format!("{verifier:?}").contains("FingerprintPinVerifier"));
let _ = sk;
}
#[test]
fn server_paths_carry_max_early_data_and_alpn() {
let alpns = vec![b"alk/call".to_vec()];
for identity in [
TlsIdentity::RawKey(Ed25519SecretKey::generate()),
TlsIdentity::SelfSigned,
] {
let config = build_rustls_server_config(&identity, &alpns)
.expect("server config builds per variant");
assert_eq!(
config.max_early_data_size,
u32::MAX,
"max_early_data_size must be u32::MAX on every server path (the 0-RTT invariant)"
);
assert_eq!(config.alpn_protocols, alpns);
assert!(
!format!("{config:?}").is_empty(),
"the config carries its verifier through the derived Debug (request-but-don't-require \
shape asserted at unit level on AcceptAnyCertVerifier itself)"
);
}
}