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:
@@ -89,9 +89,12 @@ verification — only the pin.
|
||||
|
||||
Handshake signatures are still verified (TLS 1.2/1.3, aws-lc-rs
|
||||
algorithms; Ed25519 SPKI certs route through
|
||||
`verify_tls13_signature_with_raw_key`): a stolen fingerprint cannot
|
||||
be replayed with a forged signature — the presenter must prove
|
||||
possession of the corresponding private key.
|
||||
`verify_tls13_signature_with_raw_key`): the presenter must prove
|
||||
possession of the corresponding private key, so a stolen or observed
|
||||
certificate cannot be used by a party that does not hold the matching
|
||||
key. This verifier checks proof-of-possession; the server-side
|
||||
`AcceptAnyCertVerifier` does not (see
|
||||
[server.md](server.md), OQ-TLS-09).
|
||||
|
||||
## The root-store fallback (alknet ADR-088 §5)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
| OQ-TLS-06 | ACME task shutdown surface | resolved (detached-only for v1) | low |
|
||||
| OQ-TLS-07 | iroh key surface | **resolved** (ADR-005, byte access pinned) | low |
|
||||
| OQ-TLS-08 | `quinn` → `noq` feature rename | **resolved** (ADR-003) | high |
|
||||
| OQ-TLS-09 | Server-path proof-of-possession | **open** | high |
|
||||
|
||||
## Identity & types
|
||||
|
||||
@@ -104,6 +105,44 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
[ADR-003](decisions/003-noq-replaces-quinn.md).
|
||||
- **Cross-references**: ADR-002, ADR-003, ADR-004
|
||||
|
||||
### OQ-TLS-09: Which layer owns proof-of-possession on the server path?
|
||||
|
||||
- **Origin**: docs/reviews/001-implementation-review.md §S-1 (the
|
||||
impersonation probe: a handshake with the victim's cert bytes + an
|
||||
attacker signer completes, and the server extracts the victim's
|
||||
fingerprint)
|
||||
- **Status**: open (recorded 2026-09-10)
|
||||
- **Priority**: high
|
||||
- **Question**: `AcceptAnyCertVerifier` never checks the client's
|
||||
CertificateVerify signature — the presented identity is spoofable by
|
||||
anyone holding the public cert/SPKI bytes, and the auth layer cannot
|
||||
detect it (the fingerprint it is handed *is* the victim's). alknet
|
||||
ADR-034 inherited the request-but-don't-require shape without
|
||||
recording this property. Who enforces possession?
|
||||
- **Options**:
|
||||
- **(a) Keep request-but-don't-require; the auth layer owns
|
||||
challenge-response** over the established channel, bound to the
|
||||
presented public key. No crate change; the S-1 doc note on
|
||||
`AcceptAnyCertVerifier` is the honest description until then.
|
||||
- **(b) Add a `VerifyPresentedCertVerifier` sibling**
|
||||
(request-and-verify): same nine-scheme list, permissive
|
||||
`verify_client_cert`, signature methods delegate to
|
||||
`rustls::crypto::verify_tls{12,13}_signature(_with_raw_key)` — the
|
||||
routing the client-side `FingerprintPinVerifier` already
|
||||
implements. Additive; must land before the first consumer (an API
|
||||
shape decision).
|
||||
- **(c) Make the auth layer's fingerprint resolution
|
||||
possession-checked** (a possession proof accompanies each
|
||||
fingerprint resolution). Cross-crate; the auth layer does not exist
|
||||
yet.
|
||||
- **Constraints**: the spoofable posture is pinned by
|
||||
`tests/impersonation_posture.rs` (both cert types) — any decision
|
||||
must fail or update that test together with the
|
||||
`AcceptAnyCertVerifier` doc note.
|
||||
- **Cross-references**: src/server.rs (`AcceptAnyCertVerifier`),
|
||||
src/client.rs (`FingerprintPinVerifier`),
|
||||
docs/reviews/001-implementation-review.md §S-1, alknet ADR-034
|
||||
|
||||
## Quality / process
|
||||
|
||||
### OQ-TLS-05: Test surface for the invariants
|
||||
@@ -133,5 +172,6 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
|
||||
## Deferred / Blocked
|
||||
|
||||
(none — all promoted OQs are resolved; new OQs added during review
|
||||
land here with their deferral half per `docs/sdd_process.md`)
|
||||
- OQ-TLS-09 (server-path proof-of-possession): open by design — the
|
||||
decision needs the rewrite's auth-layer design in hand (option (c))
|
||||
or an API-shape call before the first consumer (option (b)).
|
||||
@@ -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`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user