task 2: handshake-level suites — pin, fail-closed, RFC 7250 paths executed (U-3)
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.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: reviewed
|
||||
last_updated: 2026-09-10
|
||||
last_updated: 2026-09-11
|
||||
---
|
||||
|
||||
# alktls — Client side
|
||||
@@ -78,6 +78,19 @@ boundary in action.
|
||||
|
||||
**Fail-closed is structural**: known peer + fingerprint → pin;
|
||||
unknown + X.509 → CA; unknown + raw key → fail. No fourth path.
|
||||
All three outcomes are **executed** in
|
||||
`tests/handshake_behavior.rs` (real rustls handshakes over a duplex
|
||||
pair, `tcp`-gated).
|
||||
|
||||
**RFC 7250 over TCP is a negotiation gap, not a verified path**
|
||||
(OQ-TLS-10): `FingerprintPinVerifier` keeps rustls' trait-default
|
||||
`requires_raw_public_keys() == false`, so a crate-built pin client
|
||||
cannot reach a crate-built raw-key server — the handshake fails
|
||||
closed (`HandshakeFailure`). The executed raw-key-over-TCP pin
|
||||
(`raw_key_server_path_completes_with_requires_raw_verifier`) uses the
|
||||
iroh-shaped verifier (`requires_raw_public_keys() == true`) any
|
||||
raw-key-over-TCP consumer must bring. Raw-key peers that ride
|
||||
iroh/noq use those transports' own TLS and are unaffected.
|
||||
|
||||
## `FingerprintPinVerifier`
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-09-10
|
||||
last_updated: 2026-09-11
|
||||
---
|
||||
|
||||
# Open Questions
|
||||
@@ -22,6 +22,7 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
| 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 |
|
||||
| OQ-TLS-10 | RFC 7250 over TCP: cert-type negotiation gap | **open** | high |
|
||||
|
||||
## Identity & types
|
||||
|
||||
@@ -143,6 +144,66 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
src/client.rs (`FingerprintPinVerifier`),
|
||||
docs/reviews/001-implementation-review.md §S-1, alknet ADR-034
|
||||
|
||||
### OQ-TLS-10: How do RFC 7250 raw-key peers negotiate over rustls-driven TCP+TLS?
|
||||
|
||||
- **Origin**: `tasks/handshake-tests.md` (review 001 §U-3 execution):
|
||||
the executed handshake suites found that the crate's own pin client
|
||||
cannot complete a handshake against the crate's own raw-key server,
|
||||
and a raw-key client resolver cannot present itself to
|
||||
`AcceptAnyCertVerifier` — both fail with rustls' HandshakeFailure
|
||||
alert (cert-type negotiation), not a verification outcome. Verified
|
||||
empirically and against the rustls 0.23.44 sources
|
||||
(`server/hs.rs::process_cert_type_extension`,
|
||||
`client/hs.rs::process_cert_type_extension`); pinned by
|
||||
`tests/handshake_behavior.rs`.
|
||||
- **Status**: open (recorded 2026-09-11)
|
||||
- **Priority**: high
|
||||
- **Mechanism** (rustls 0.23.44, verified):
|
||||
- A raw-key *server* resolver (`only_raw_public_keys() == true`)
|
||||
requires the client to offer `server_certificate_types =
|
||||
[RawPublicKey]`. rustls' client sends that offer only when the
|
||||
client verifier overrides `requires_raw_public_keys() == true`.
|
||||
- This crate's `FingerprintPinVerifier` keeps the trait default
|
||||
(`false`), and `AcceptAnyCertVerifier` never overrides it either.
|
||||
- Consequently: crate-pin-client ↔ crate-raw-key-server fails; and a
|
||||
raw-key *client* resolver (offering `[RawPublicKey]` client cert
|
||||
types) fails against `AcceptAnyCertVerifier` (the
|
||||
`(false, true, false)` arm → `IncorrectCertificateTypeExtension`).
|
||||
The fail-closed rule still holds — no path downgrades — but the
|
||||
raw-key-over-TCP interop the extraction implies does not exist
|
||||
yet.
|
||||
- **Context**: the raw-key paths that work today are iroh's (its
|
||||
built-in TLS overrides `requires_raw_public_keys() == true` on both
|
||||
verifiers — `iroh/src/tls/verifier.rs`) and the QUIC path
|
||||
(noq/iroh negotiate cert types differently from rustls' TCP
|
||||
state machine). alknet's extracted client never negotiated
|
||||
raw-key-over-TCP either (no override in alknet-tls) — behavior
|
||||
preservation holds; the gap is inherited, not introduced.
|
||||
- **Options**:
|
||||
- **(a) Document the gap** — raw-key peers ride iroh/noq (their own
|
||||
TLS stacks), not rustls TCP+TLS; TCP+TLS is the X.509 transport.
|
||||
No code change; the handshake tests pin the executed behavior.
|
||||
- **(b) Add a `requires_raw_public_keys() == true` override** on
|
||||
`FingerprintPinVerifier` for `ed25519:` pins (the iroh shape).
|
||||
Changes the pin verifier's negotiation: a pin client could reach
|
||||
raw-key servers — but then an X.509 remote pinned by `SHA256:`
|
||||
could no longer negotiate with the same client config (the offer
|
||||
would exclude X509), so the two pin formats cannot share one
|
||||
client config. An API-shape decision before the first consumer.
|
||||
- **(c) Add a server-side verifier that overrides
|
||||
`requires_raw_public_keys() == true`** (raw-key-only client auth),
|
||||
additive like OQ-TLS-09's option (b); pairs with it if mandatory
|
||||
raw-key client auth is wanted.
|
||||
- **Constraints**: the executed behavior is pinned both ways by
|
||||
`tests/handshake_behavior.rs` (`raw_key_server_path_completes_…`
|
||||
with an iroh-shape verifier;
|
||||
`raw_key_client_resolver_fails_against_accept_any_cert_verifier`) —
|
||||
any decision must update those tests together with this OQ.
|
||||
- **Cross-references**: src/client.rs (`FingerprintPinVerifier` — the
|
||||
default `false`), src/server.rs (`RawKeyCertResolver`,
|
||||
`AcceptAnyCertVerifier`), review 001 §N-4 (the client-resolver trap),
|
||||
iroh `iroh/src/tls/verifier.rs` (the working prior art)
|
||||
|
||||
## Quality / process
|
||||
|
||||
### OQ-TLS-05: Test surface for the invariants
|
||||
@@ -174,4 +235,9 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
|
||||
- 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)).
|
||||
or an API-shape call before the first consumer (option (b)).
|
||||
- OQ-TLS-10 (RFC 7250 over TCP negotiation gap): open by design —
|
||||
behavior-preserving (the gap is inherited from alknet); deciding
|
||||
needs the first raw-key-over-TCP consumer in hand (options (b)/(c)
|
||||
are API-shape decisions), or the X.509-only TCP posture is
|
||||
documented as-is (option (a)).
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: reviewed
|
||||
last_updated: 2026-09-10
|
||||
last_updated: 2026-09-11
|
||||
---
|
||||
|
||||
# alktls — Server side
|
||||
@@ -108,6 +108,18 @@ 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
|
||||
`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).
|
||||
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.
|
||||
|
||||
## The ACME path
|
||||
|
||||
For `TlsIdentity::Acme`, `new` (feature `acme`):
|
||||
|
||||
Reference in New Issue
Block a user