From 67f45ae19230b7f63fc8bdbfb6d00d132baabc0c Mon Sep 17 00:00:00 2001 From: UnbreakableMJ Date: Thu, 18 Jun 2026 11:27:53 +0300 Subject: [PATCH] test(vault-api): live X25519MLKEM768 handshake gate (v0.1 gate #2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pqc unit tests only exercise our own half of the exchange. This adds live_handshake_negotiates_x25519mlkem768 — an #[ignore]d interop test that drives a real TLS 1.3 handshake with client_config() against Cloudflare's PQC host (pq.cloudflareresearch.com) and asserts the *negotiated* key- exchange group is X25519MLKEM768, proving the hand-rolled hybrid wire layout interoperates with an independent server. Confirmed passing: cargo test -p vault-api --features pqc -- --ignored live_handshake Needs network, so it's #[ignore]d like login_sync and never runs in CI. - docs/pqc.md: a "Live handshake test" section + status reconcile (this gate and the fuzz soak are done; only the daily-driver attestation remains). - RELEASING.md: tick gate #2, cite the run. - CHANGELOG.md: [Unreleased] note. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 10 ++++++++++ RELEASING.md | 5 ++++- crates/vault-api/src/pqc.rs | 39 +++++++++++++++++++++++++++++++++++++ docs/pqc.md | 23 ++++++++++++++++++---- 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31a9a92..6cdc1e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ range may break in any release. ### Added +- **Live PQC handshake gate met (RELEASING.md gate #2).** Added + `live_handshake_negotiates_x25519mlkem768`, an `#[ignore]`d interop test that + drives a real TLS 1.3 handshake with the `pqc` client config against + Cloudflare's PQC host (`pq.cloudflareresearch.com`) and asserts the + *negotiated* group is X25519MLKEM768 — confirming the hand-rolled hybrid wire + layout interoperates with an independent server, not just our own halves. Run + with `cargo test -p vault-api --features pqc -- --ignored live_handshake` + (`docs/pqc.md`). Second of the v0.1 operational gates cleared; only the §11.2 + two-week daily-driver attestation remains. + - **EncString fuzz soak passed (PRD §11.4 / RELEASING.md gate #1).** A ≥ 24 h libFuzzer soak of the `EncString` type-2 parser completed with **0 findings**: 8,874,210,317 executions over 86,401 s (~102.7 k exec/s), coverage flat at 312 diff --git a/RELEASING.md b/RELEASING.md index b395da5..bb542ef 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -17,9 +17,12 @@ do the mechanical cut below. (see `docs/fuzzing.md`). Any reproducer under `fuzz/artifacts/` blocks the tag until fixed. **Done 2026-06-18:** 8.87 B executions over 86,401 s, 0 findings, exit 0 — full run logged in `docs/fuzzing-report.md`. -- [ ] **Live PQC handshake** — build with PQC and confirm an X25519MLKEM768 +- [x] **Live PQC handshake** — build with PQC and confirm an X25519MLKEM768 handshake against a PQC-enabled endpoint: `cargo build -p vault-agent --features pqc` (see `docs/pqc.md`). + **Done 2026-06-18:** `live_handshake_negotiates_x25519mlkem768` + (`cargo test -p vault-api --features pqc -- --ignored live_handshake`) + negotiated X25519MLKEM768 against `pq.cloudflareresearch.com`. - [ ] **End-to-end** (PRD §11.1) — `register` / `login` / `sync` / `get` against both bitwarden.com and a Vaultwarden container (`docs/m2-vaultwarden.md`). - [ ] **Daily-driver** (PRD §11.2) — two consecutive weeks of maintainer use with diff --git a/crates/vault-api/src/pqc.rs b/crates/vault-api/src/pqc.rs index bafb189..e478bfb 100644 --- a/crates/vault-api/src/pqc.rs +++ b/crates/vault-api/src/pqc.rs @@ -238,4 +238,43 @@ mod tests { .expect("at least one kx group"); assert_eq!(first.name(), NamedGroup::X25519MLKEM768); } + + /// Live gate (PRD §11.4 / `RELEASING.md`): drive a real TLS 1.3 handshake + /// with the [`client_config`] against a PQC-enabled server and confirm the + /// hybrid group is actually *negotiated*, not merely offered. Cloudflare's + /// research host supports X25519MLKEM768; the unit tests above only exercise + /// our half of the exchange, so this is the one check that the wire layout + /// interoperates with an independent server implementation. + /// + /// `#[ignore]`d — needs network. Run with: + /// `cargo test -p vault-api --features pqc -- --ignored live_handshake`. + #[test] + #[ignore = "live network: handshakes with pq.cloudflareresearch.com:443"] + fn live_handshake_negotiates_x25519mlkem768() { + use rustls::pki_types::ServerName; + use std::net::TcpStream; + + const HOST: &str = "pq.cloudflareresearch.com"; + + let config = Arc::new(client_config().expect("config builds")); + let server_name = ServerName::try_from(HOST).expect("valid server name"); + let mut conn = + rustls::ClientConnection::new(config, server_name).expect("client connection"); + let mut sock = TcpStream::connect((HOST, 443)).expect("tcp connect"); + + // Drive ClientHello → ServerHello…Finished → client Finished to + // completion; no application data needed to fix the negotiated group. + conn.complete_io(&mut sock) + .expect("tls handshake completes"); + + let group = conn + .negotiated_key_exchange_group() + .expect("a key-exchange group was negotiated"); + assert_eq!( + group.name(), + NamedGroup::X25519MLKEM768, + "server negotiated {:?}, not the hybrid PQC group", + group.name(), + ); + } } diff --git a/docs/pqc.md b/docs/pqc.md index a74e384..8b20914 100644 --- a/docs/pqc.md +++ b/docs/pqc.md @@ -17,6 +17,20 @@ When enabled, the client offers X25519MLKEM768 first and the classical groups don't yet — the handshake silently falls back to a classical group, so enabling the feature is safe. PQC is TLS 1.3 only. +## Live handshake test + +The unit tests in `crates/vault-api/src/pqc.rs` exercise only our half of the +exchange (KEM round-trip, share/secret layout, config ordering). The +`live_handshake_negotiates_x25519mlkem768` test is the interop gate: it drives a +real TLS 1.3 handshake with [`client_config`] against Cloudflare's PQC research +host (`pq.cloudflareresearch.com`) and asserts the *negotiated* key-exchange +group is X25519MLKEM768 — proving the wire construction interoperates with an +independent server. It is `#[ignore]`d (needs network); run it with: + +```sh +cargo test -p vault-api --features pqc -- --ignored live_handshake +``` + ## Why we hand-roll it (and don't use aws-lc-rs) rustls only ships X25519MLKEM768 through its **aws-lc-rs** provider. aws-lc-rs @@ -53,9 +67,10 @@ concatenates the two secrets (PQ first). This mirrors rustls's own ## Status (PRD §12 M7) -This satisfies the M7 "PQC transport feature flag" item. Still pending for the -`v0.1` tag: making PQC a tested live handshake against a PQC-enabled server, the -≥ 24 h EncString fuzz soak (`docs/fuzzing.md`), the broader hardening pass, and -the §11.2 two-week daily-driver attestation. +This satisfies the M7 "PQC transport feature flag" item, and the live handshake +gate is now met (see above — `live_handshake_negotiates_x25519mlkem768` confirms +X25519MLKEM768 against Cloudflare). The ≥ 24 h EncString fuzz soak is also done +(`docs/fuzzing-report.md`). Still pending for the `v0.1` tag: the §11.2 two-week +daily-driver attestation. [`ml-kem`]: https://crates.io/crates/ml-kem