task 1: S-1 remediation — no-pop posture doc + OQ-TLS-09 + permanent impersonation pin

- AcceptAnyCertVerifier doc: the presented CertificateVerify signature
  is not verified (no proof-of-possession) — the server-extracted
  fingerprint is attacker-suppliable from observed public cert/SPKI
  bytes; states the two safe patterns (auth-layer challenge-response /
  a verifying verifier) and points at OQ-TLS-09
- FingerprintPinVerifier doc (N-1): fixed the "stolen-but-stale
  fingerprint" phrasing (the cert is presented fresh each handshake;
  the signature check defeats a stolen/observed cert used by a party
  without the private key) and added the server-verifier cross-reference
- OQ-TLS-09 recorded (open, high): which layer owns server-path
  proof-of-possession — three options; deferral noted (needs the
  auth-layer design or an API call before the first consumer)
- tests/impersonation_posture.rs (tcp-gated): the S-1 probe made
  permanent, both variants — X.509 victim cert + attacker key and RFC
  7250 victim SPKI + attacker key complete the handshake, application
  data flows, and the server extracts the victim's fingerprint; any
  future pop change must fail/update this test with the doc + OQ
- server.md / client.md synced with the same posture
- task note: the review's N-4 parenthetical ("alknet's client resolver
  offers both types") is inaccurate — rustls 0.23.41/0.23.44 offer
  [RawPublicKey] iff the resolver's only_raw_public_keys() is true;
  task 6 should write N-4 from the rustls sources

Verified: cargo test 68 default / 77 all-features (+2) green; clippy
-D warnings clean (default + all-features); fmt clean; cargo doc
--no-deps warning-free
This commit is contained in:
2026-09-10 22:00:57 +00:00
parent d7db6b17a6
commit e86b8ba1b5
7 changed files with 414 additions and 21 deletions
+19 -1
View File
@@ -79,9 +79,27 @@ fingerprint is matched against peer records by the auth layer
the TLS crate hands over the fingerprint string; peer resolution is
not a TLS concern (ADR-005).
**The presented signature is not verified (no proof-of-possession)**
review 001's S-1: the client's CertificateVerify signature is never
checked against the presented cert's public key, so any party holding
a peer's public cert bytes (X.509) or SPKI (RFC 7250) can complete a
handshake *as that peer*, and the server hands the auth layer the
victim's fingerprint. The auth layer cannot detect this — the
fingerprint it is handed *is* the victim's. Until the caller enforces
possession, treat the extracted fingerprint as an unauthenticated
claim, not proof of identity. Two patterns make it safe (the auth
layer owns either): challenge-response over the established channel
bound to the presented public key, or a verifier that verifies the
CertificateVerify (the routing `FingerprintPinVerifier` implements —
tracked as OQ-TLS-09; option (b) there is additive but must land
before the first consumer). The spoofable posture is pinned by
`tests/impersonation_posture.rs` (both cert types) — a change here
must fail or update that test together with this doc.
Server-side only: this must not be reused as a client-side
`ServerCertVerifier` — client-side verification is alknet ADR-034's
selection matrix (see [client.md](client.md)).
selection matrix (see [client.md](client.md)), and unlike the
client-side pin verifier this type has no proof-of-possession check.
## `RawKeyCertResolver`