ADR-007: RFC 7250 cert-type negotiation — the offer follows the identity (OQ-TLS-10 resolved)

Resolve the cert-type negotiation gap (review 001 §U-3, OQ-TLS-10) by
deviation from alknet: the gap was a defect in the prior art (alknet's
code never delivered its spec's raw-key-over-TCP promise — ADR-082
"works for both QUIC and TCP+TLS"), not behavior to preserve.

- FingerprintPinVerifier::requires_raw_public_keys() derives from the
  pin format: ed25519: -> true (offer [RawPublicKey]), SHA256: ->
  false (X.509 offer). Crate pin client now completes against the
  crate raw-key server; SHA256: pins negotiate unchanged.
- RawKeyClientCertResolver presents the SPKI under the X.509 offer
  (only_raw_public_keys() == false): a raw-only client offer can only
  negotiate against a requires_raw server verifier, and
  AcceptAnyCertVerifier correctly stays on the default (accepts both
  cert types). The server extracts the ed25519: fingerprint from the
  SPKI bytes either way.
- Fail-closed preserved and strengthened: an ed25519: pin against an
  X.509 server now aborts at negotiation (suite 2b), never a
  downgrade; no API change (no public signature affected; the fix is
  invisible to consumers apart from working handshakes).
- tests/handshake_behavior.rs: suite 3 now runs crate-native (no
  custom iroh-shaped verifier), new negotiation fail-closed suite,
  suite 3b inverted to end-to-end success; invariant_pins.rs
  resolver-offer assertions flipped; unused imports dropped.
- Docs: ADR-007 written; OQ-TLS-10 -> resolved-by-deviation;
  client.md/server.md/overview/README/task postscript synced
  (incl. the strict-foreign-server limit in ADR-007 §Limits).

Verification: cargo test 81 / --features tcp 94 / --all-features
105 green; clippy -D warnings clean (default + all-features); fmt
clean; cargo doc warning-free.
This commit is contained in:
2026-09-11 10:26:28 +00:00
parent 4a4fae64af
commit 49d4432247
10 changed files with 390 additions and 229 deletions
+13 -9
View File
@@ -108,17 +108,21 @@ certificate: the SPKI DER (Ed25519 OID + 32-byte key) is the "cert",
`only_raw_public_keys() == true`, and the signing key is the shared
`Ed25519SigningKey` helper (`signing.rs`).
**Interop note (OQ-TLS-10, executed in
**Interop note (ADR-007, executed in
`tests/handshake_behavior.rs`)**: a raw-key server resolver requires
the client to offer `[RawPublicKey]` *server* cert types, which
rustls sends only when the client verifier overrides
`requires_raw_public_keys() == true` (iroh's verifier does; this
crate's server-side `AcceptAnyCertVerifier` also keeps the default
`false`, and a raw-key *client* resolver offering `[RawPublicKey]`
client cert types is rejected by it — N-4's trap, also executed).
the client to offer `[RawPublicKey]` *server* cert types, which rustls
sends only when the client verifier overrides
`requires_raw_public_keys() == true`. The crate's own
`FingerprintPinVerifier` now does exactly that for `ed25519:` pins
(the offer follows the pin format), so a crate pin client completes
against this raw-key server
(`raw_key_server_path_completes_with_crate_pin_client`). A raw-key
*client* identity presents its SPKI under the X.509 offer, which
`AcceptAnyCertVerifier` (`requires_raw_public_keys() == false`
correctly; it accepts both cert types) accepts end-to-end
(`raw_key_client_presents_spki_and_server_extracts_fingerprint`).
Raw-key peers riding iroh/noq are unaffected (their TLS stacks own
the negotiation); rustls-driven TCP+TLS is the X.509 transport until
OQ-TLS-10 decides otherwise.
their negotiation).
## The ACME path