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.
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.