task 2: handshake-level suites — pin, fail-closed, RFC 7250 paths executed (U-3)

tests/handshake_behavior.rs (tcp-gated, tokio duplex + tokio-rustls,
no new deps) turns the fail-closed / pin / raw-key language into
executed behavior:

- pin match: X.509 server + SHA256 pin -> handshake completes, app
  data round-trips, server extracts the client cert fingerprint
- pin mismatch: wrong pin -> handshake error (the pin IS the anchor)
- fail closed: remote_identity None + raw-key server -> HandshakeFailure
- raw-key server path end-to-end: completes with the iroh-shaped
  client verifier (requires_raw_public_keys == true); presented cert
  asserted to be the SPKI carrying the raw Ed25519 key
- N-4's interop trap executed: raw-key client resolver vs
  AcceptAnyCertVerifier -> IncorrectCertificateTypeExtension alert

Major finding, recorded as OQ-TLS-10 (open): a crate-built pin
client cannot reach a crate-built raw-key server over rustls TCP+TLS
— the raw-key resolver requires the client to offer [RawPublicKey]
server cert types, sent only when the client verifier overrides
requires_raw_public_keys() == true. FingerprintPinVerifier keeps the
trait default false (AcceptAnyCertVerifier too); iroh's verifier
overrides true on both sides. Gap inherited from alknet
(behavior-preserving); pinned both ways by the suite.

client.md / server.md carry the interop notes; task file updated
(premise adjustments documented in Notes, summary filled).

Verification: 81 default / 91 tcp / 99 all-features tests green
(+5 new), clippy -D warnings clean both configs, fmt clean,
cargo doc warning-free, taskgraph validate 14 tasks.
This commit is contained in:
2026-09-11 08:17:28 +00:00
parent d82956385b
commit 4a4fae64af
5 changed files with 511 additions and 7 deletions
+64 -3
View File
@@ -1,7 +1,7 @@
---
id: handshake-tests
name: Handshake-level test suite — pin, fail-closed, raw-key path executed (U-3)
status: pending
status: completed
depends_on: []
scope: moderate
risk: medium
@@ -84,8 +84,69 @@ The four handshake-level gaps (review 001 §U-3):
## Notes
> Agent fills this during implementation.
> **Major finding during implementation (recorded as OQ-TLS-10):** the
> task's suite premises 1 and 3 did not hold as written. A temporary
> probe (the review's methodology, deleted after the run) found:
>
> - **A crate-built pin client cannot complete a handshake against a
> crate-built raw-key server** (HandshakeFailure). Mechanism,
> verified in the rustls 0.23.44 sources
> (`server/hs.rs::process_cert_type_extension`,
> `client/hs.rs::process_cert_type_extension`): the raw-key server
> resolver requires the client to offer
> `server_certificate_types = [RawPublicKey]`, which rustls sends
> only when the client verifier overrides
> `requires_raw_public_keys() == true`. `FingerprintPinVerifier`
> keeps the trait default `false` (as does
> `AcceptAnyCertVerifier`). iroh's verifier overrides `true` on both
> sides (`iroh/src/tls/verifier.rs`) — the working prior art. The
> gap is inherited from alknet (no override there either) —
> behavior-preserving, but the raw-key-over-TCP interop the
> extraction implies does not exist yet.
> - **Suite 3's premise adjusted**: the raw-key server path completes
> only with an iroh-shaped verifier (`requires_raw_public_keys()
> == true`); the suite pins that working shape (with
> `NoClientCertResolver` client auth) and asserts the presented
> SPKI carries the raw Ed25519 key.
> - **Suite 1's premise adjusted**: the crate pin client ↔ raw-key
> server shape was replaced by the pin client ↔ X.509 server
> (`SHA256:` pin, which the crate's verifier can negotiate), pin
> match + mismatch. The executed fail-closed (suite 2) is
> mechanically the same HandshakeFailure (raw-key server needs a
> raw-key offer the CA-path client never makes) — the alert is
> asserted, the no-downgrade outcome is what's pinned.
> - **N-4's trap executed as suite 3b**: raw-key client resolver ↔
> `AcceptAnyCertVerifier` fails (client offers
> `[RawPublicKey]` → `IncorrectCertificateTypeExtension`).
>
> OQ-TLS-10 recorded (open, options a/b/c, deferral rationale);
> client.md and server.md carry the interop notes.
## Summary
> Agent fills this on completion.
**Landed as `tests/handshake_behavior.rs`** (`tcp`-gated, 5 suites;
no new deps — tokio duplex + tokio-rustls under the existing feature):
1. `pin_match_completes_and_server_extracts_client_fingerprint`
X.509 server, client pins the matching `SHA256:` fingerprint →
handshake completes, app data round-trips, server extracts the
client cert's fingerprint (request-but-don't-require, executed).
2. `pin_mismatch_fails_the_handshake` — wrong pin → handshake error
carrying the pin verifier's mismatch message.
3. `unknown_raw_key_remote_fails_closed``remote_identity: None` +
raw-key server → HandshakeFailure (fail closed, executed).
4. `raw_key_server_path_completes_with_requires_raw_verifier` — the
RFC 7250 server path end-to-end with the iroh-shaped client
verifier: handshake completes, no client cert presented, and the
presented server cert is verified to be the SPKI carrying the raw
Ed25519 public key.
5. `raw_key_client_resolver_fails_against_accept_any_cert_verifier`
N-4's interop trap executed (raw-key client cert types rejected).
Plus **OQ-TLS-10** (open — the cert-type negotiation gap above),
server.md/client.md synced, frontmatter → completed.
**Verification:** 81 default / 91 tcp / 99 all-features tests green
(+5 new handshake suites), clippy `-D warnings` clean (default +
all-features), fmt clean, `cargo doc` warning-free, `taskgraph
validate` 14 tasks.