crates/gunnar-grpc/tests/tls13_only.rs
//! **The TLS 1.3 floor, and the plaintext floor.**
//!
//! Two rulings are pinned here.
//!
//! * The owner ruled *WireGuard AND mTLS*, TLS 1.3 only, no downgrade and no
//! negotiation surface. tonic 0.14 has no version knob — both halves call
//! `with_safe_default_protocol_versions()`, which is 1.2 AND 1.3 — so the
//! enforcement is the process-wide rustls provider, and these tests are what
//! say it is really there.
//! * A gunnar-to-gunnar call never leaves a machine in the clear. `plain_channel`
//! is loopback-only; the twin's replication lane, which carries the KEK as
//! seq 1 of the log, is the caller this is about.
//!
//! **Every assertion below can fail**, which is the point the estate keeps
//! re-learning: the filter is checked against an unfiltered provider that
//! really does carry TLS 1.2 suites, and the loopback predicate is asserted in
//! both directions. A test that could only ever pass is the health line that
//! cannot fail.
//!
//! `refuse_unless_tls13_only` reads the PROCESS default, and `install_default`
//! is once per process — so its two answers live in two separate test binaries:
//! this one for the clean answer, `tls13_provider_lost.rs` for the refusal.
use gunnar_grpc::client_signer::{is_loopback_target, plain_channel, tls_domain_for};
/// The filter is not vacuous: `ring`'s default provider really does carry
/// suites below TLS 1.3, and [`gunnar_grpc::tls13_only_provider`] really does
/// remove them.
///
/// Without the first half this file would pass against a filter that did
/// nothing at all.
#[test]
fn the_filter_removes_suites_that_were_actually_there() {
let unfiltered = rustls::crypto::ring::default_provider();
let below: Vec<_> = unfiltered
.cipher_suites
.iter()
.filter(|s| s.version().version != rustls::ProtocolVersion::TLSv1_3)
.collect();
assert!(
!below.is_empty(),
"ring's default provider carries no sub-TLS-1.3 suite, so filtering it proves nothing. \
If rustls has dropped TLS 1.2 entirely this test should be deleted, not weakened."
);
let filtered = gunnar_grpc::tls13_only_provider();
let still_below: Vec<_> = filtered
.cipher_suites
.iter()
.filter(|s| s.version().version != rustls::ProtocolVersion::TLSv1_3)
.map(|s| format!("{:?}", s.suite()))
.collect();
assert!(
still_below.is_empty(),
"tls13_only_provider still offers {still_below:?}, so a peer could negotiate TLS 1.2"
);
assert!(
!filtered.cipher_suites.is_empty(),
"a provider with no suites cannot handshake at all"
);
}
/// The bulk-transfer preference the owner asked for, and it follows the CPU
/// rather than a constant.
#[test]
fn the_preferred_suite_follows_the_hardware() {
let provider = gunnar_grpc::tls13_only_provider();
let first = provider
.cipher_suites
.first()
.expect("at least one TLS 1.3 suite")
.suite();
if gunnar_grpc::aes_is_accelerated() {
assert_eq!(
first,
rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
"this CPU has AES-NI and PCLMULQDQ, so AES-128-GCM must be preferred: it is the \
hardware path, and WireGuard's ChaCha20 underneath then runs on different \
execution units instead of queueing behind the record layer"
);
} else {
assert_eq!(
first,
rustls::CipherSuite::TLS13_CHACHA20_POLY1305_SHA256,
"no AES-NI on this CPU, so a software AES-GCM would be the slower half and the \
layering argument inverts"
);
}
// AES-256-GCM is kept but never preferred: same hardware path, more rounds.
assert_ne!(first, rustls::CipherSuite::TLS13_AES_256_GCM_SHA384);
assert!(
gunnar_grpc::preferred_bulk_suite().contains(if gunnar_grpc::aes_is_accelerated() {
"AES_128_GCM"
} else {
"CHACHA20"
}),
"the reported preference must be the one actually installed: {}",
gunnar_grpc::preferred_bulk_suite()
);
}
/// With the 1.3-only provider installed, the pre-handshake check passes.
///
/// This test owns the process's `install_default`, which is why nothing else in
/// this file builds a listener or a channel.
#[test]
fn the_check_passes_when_the_right_provider_won() {
gunnar_grpc::ensure_crypto_provider();
let offending = gunnar_grpc::non_tls13_suites().expect("a provider is installed");
assert!(
offending.is_empty(),
"installed provider still offers {offending:?}"
);
gunnar_grpc::refuse_unless_tls13_only().expect("the 1.3-only provider must satisfy the check");
}
/// `plain_channel` serves this box and refuses every other one, **by name**.
///
/// The refusal names the reason (the KEK rides this log) and the way across
/// (`mutual_channel`), because a refusal that only says "no" gets worked around.
// A tokio test because the loopback half really builds a channel, and
// `connect_lazy` registers with the reactor. The refusal half needs no runtime
// — it never gets that far, which is itself the property.
#[tokio::test]
async fn plaintext_never_leaves_the_box() {
for here in [
"127.0.0.1:50051",
"localhost:50051",
"[::1]:50051",
"http://127.0.0.1:50051",
"127.0.0.1",
] {
assert!(is_loopback_target(here), "{here} is this box");
assert!(
plain_channel(here).is_ok(),
"{here} is loopback and must still be dialable in the clear"
);
}
for elsewhere in [
"10.77.0.1:50051", // the WireGuard address of a primary
"192.168.1.9:50051", // the underlay
"http://10.77.0.1:50051", // a scheme does not make it local
"primary.example:50051", // a name is not evidence of anything
"127.0.0.1.example:50051", // and neither is a name that looks like one
] {
assert!(
!is_loopback_target(elsewhere),
"{elsewhere} is NOT this box"
);
let err =
plain_channel(elsewhere).expect_err("a call off this box must not go in the clear");
assert!(
err.contains("mutual_channel"),
"the refusal must name the way across: {err}"
);
assert!(
err.contains("KEK"),
"the refusal must say what is at stake: {err}"
);
}
}
/// The certificate is checked against a HOST, never `host:port`.
///
/// `10.77.0.1:50051` is in no certificate anywhere; passing it through would
/// turn every mutual dial into a name-mismatch nobody could explain.
#[test]
fn the_tls_domain_is_the_host_alone() {
assert_eq!(tls_domain_for("10.77.0.1:50051"), "10.77.0.1");
assert_eq!(tls_domain_for("https://10.77.0.1:50051"), "10.77.0.1");
assert_eq!(tls_domain_for("primary.example:50051"), "primary.example");
assert_eq!(tls_domain_for("10.77.0.1"), "10.77.0.1");
assert_eq!(tls_domain_for("[fd00::1]:50051"), "fd00::1");
}