ADR-008: server-path possession verification — the verifying verifier is the default (OQ-TLS-09 resolved)
Close review 001 §S-1: the default client-cert verifier never checked the client's CertificateVerify, so anyone holding a peer's *public* cert/SPKI bytes (public by design — peers publish them to be dialable) could complete a handshake as that peer, and the auth layer could not detect it. The consumer designs are known (X.509 and raw-key TCP/QUIC endpoints with identity-bearing clients), so implementing now — the zero-consumer moment — avoids the guaranteed breaking republish of flipping the default later. - VerifyPresentedCertVerifier (new): request, don't require, verify possession — permissive verify_client_cert (self-signed chains and bare SPKIs stay valid presentation) + CertificateVerify routing by presented cert kind (Ed25519 SPKI -> verify_tls13_signature_with_ raw_key both TLS versions; X.509 -> standard route), the same routing FingerprintPinVerifier implements. Nine-scheme list verbatim (shared fn, exact-list pin covers both). - Default on every TlsServerConfig path — X509 / RawKey / SelfSigned / ACME (the verifier install is crate-side rustls in new_acme, not rustls-acme's). - AcceptAnyCertVerifier stays public as the explicit no-pop escape hatch, no longer installed by any crate path. - tests/impersonation_posture.rs: four pins — default rejects the attacker (X.509: UnsupportedSignatureAlgorithmForPublicKeyContext; raw-key: BadSignature), escape hatch still accepts + extracts the victim's fingerprint (both cert types). - tests/handshake_behavior.rs: suites 4/4b — possession-checked legit clients (raw-key pin vs raw-key server; X.509 client vs X.509 server) complete and the server extracts the fingerprint; suite 3b doc updated. - Docs: ADR-008 written; OQ-TLS-09 -> resolved (option (b)); ADR-007 §Limits deferral retired to not-planned; server.md/client.md invariants/README/overview synced. Verification: cargo test 81 / --features tcp 95 / --all-features 104 green; clippy -D warnings clean (default + all-features); fmt clean; cargo doc warning-free.
This commit is contained in:
+42
-39
@@ -59,47 +59,46 @@ test (ADR-006):
|
||||
- **`rustls::crypto::aws_lc_rs::default_provider()`** as the crypto
|
||||
provider on all paths (alknet ADR-084). Never `ring`, never the
|
||||
process-default provider, without a new ADR.
|
||||
- **`AcceptAnyCertVerifier::supported_verify_schemes()`** returns
|
||||
- **The nine-scheme `supported_verify_schemes()`** (shared by
|
||||
`VerifyPresentedCertVerifier` and `AcceptAnyCertVerifier`) returns
|
||||
ED25519 + ECDSA P-256/P-384 + RSA PSS (SHA256/384/512) + RSA PKCS1
|
||||
(SHA256/384/512) — nine schemes, verbatim, pinned by an exact-list
|
||||
integration test.
|
||||
- **The default client-cert verifier verifies possession**
|
||||
(ADR-008): `VerifyPresentedCertVerifier` is installed on every
|
||||
path; the CertificateVerify is checked against the presented
|
||||
cert's public key. `AcceptAnyCertVerifier` exists only as the
|
||||
explicit escape hatch.
|
||||
- **`acme-tls/1` ALPN append** for the ACME path only, done by the
|
||||
crate, not the caller (alknet ADR-027 §7).
|
||||
- **Non-empty root store** — the client CA path merges `webpki-roots`
|
||||
when the platform store is empty (see [client.md](client.md)).
|
||||
|
||||
## `AcceptAnyCertVerifier`
|
||||
## `VerifyPresentedCertVerifier` (the default) and `AcceptAnyCertVerifier` (the escape hatch)
|
||||
|
||||
The server-side client-cert verifier: **request-but-don't-require**.
|
||||
It asks for a client cert (X.509 or RFC 7250 raw key) so the caller
|
||||
can extract the fingerprint via `peer_identity()`, but does not
|
||||
require one and does not verify the presented cert against a CA. The
|
||||
fingerprint is matched against peer records by the auth layer
|
||||
(`IdentityProvider::resolve_from_fingerprint`) *outside* this crate —
|
||||
the TLS crate hands over the fingerprint string; peer resolution is
|
||||
not a TLS concern (ADR-005).
|
||||
The server-side client-cert verifier: **request, don't require, verify
|
||||
possession** (ADR-008, resolving OQ-TLS-09).
|
||||
[`VerifyPresentedCertVerifier`](decisions/008-server-path-possession-verification.md)
|
||||
is the default on every `TlsServerConfig` path (X509 / RawKey /
|
||||
SelfSigned / ACME). It asks for a client cert (X.509 or RFC 7250 raw
|
||||
key) so the caller can extract the fingerprint via `peer_identity()`,
|
||||
does not require one, and does not verify the presented cert against a
|
||||
CA — self-signed chains and bare SPKIs are valid presentation. The
|
||||
client's **CertificateVerify signature is verified** against the
|
||||
presented cert's public key (Ed25519 SPKIs route through
|
||||
`verify_tls13_signature_with_raw_key`; X.509 through the standard
|
||||
route): the extracted fingerprint is possession-checked — presenting a
|
||||
victim's public bytes under an attacker's key fails the handshake.
|
||||
Who the fingerprint maps to remains the auth layer's 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)), and unlike the
|
||||
client-side pin verifier this type has no proof-of-possession check.
|
||||
**The escape hatch**: `AcceptAnyCertVerifier` is the documented no-pop
|
||||
verifier — same request-not-require shape, no CertificateVerify check,
|
||||
so the fingerprint it extracts is attacker-suppliable (S-1). Install
|
||||
it explicitly only when a deployment deliberately wants that posture.
|
||||
Both postures are pinned by `tests/impersonation_posture.rs` (default
|
||||
rejects the attacker, escape hatch accepts — both cert types); a
|
||||
change must update that test together with this doc.
|
||||
|
||||
## `RawKeyCertResolver`
|
||||
|
||||
@@ -117,10 +116,12 @@ sends only when the client verifier overrides
|
||||
(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`).
|
||||
*client* identity presents its SPKI under the X.509 offer, which the
|
||||
verifiers (`requires_raw_public_keys() == false` — correctly; both
|
||||
accept X.509-or-raw cert types) accept end-to-end, the default
|
||||
possession-verifying the presentation
|
||||
(`raw_key_client_presents_spki_and_server_extracts_fingerprint`,
|
||||
`raw_key_client_vs_raw_key_server_default_verifier_checks_possession`).
|
||||
Raw-key peers riding iroh/noq are unaffected (their TLS stacks own
|
||||
their negotiation).
|
||||
|
||||
@@ -169,9 +170,10 @@ Lifecycle semantics:
|
||||
filters per endpoint type — alknet ADR-086 §3); the crate appends
|
||||
only `acme-tls/1` on the ACME path.
|
||||
- No handshake: verifier selection and handshake outcomes on the
|
||||
*server* side are `AcceptAnyCertVerifier` + the caller's
|
||||
fingerprint extraction; a rejected handshake is the transport's
|
||||
error, not `TlsError`.
|
||||
*server* side are the default `VerifyPresentedCertVerifier` (or the
|
||||
explicitly-installed escape hatch) + the caller's fingerprint
|
||||
extraction; a rejected handshake is the transport's error, not
|
||||
`TlsError`.
|
||||
- No peer resolution: the extracted fingerprint string goes to the
|
||||
caller; `PeerEntry`/`AuthPolicy` live in the auth layer.
|
||||
|
||||
@@ -183,4 +185,5 @@ Lifecycle semantics:
|
||||
spec this doc mirrors
|
||||
- ADR-001 (invariants), ADR-002 (`TlsError`), ADR-003 (`for_noq`),
|
||||
ADR-004 (accessors), ADR-005 (identity types), ADR-006 (modules,
|
||||
tests)
|
||||
tests), ADR-007 (cert-type negotiation), ADR-008 (possession
|
||||
verification)
|
||||
Reference in New Issue
Block a user