ADR-007: RFC 7250 cert-type negotiation — the offer follows the identity (OQ-TLS-10 resolved)
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.
This commit is contained in:
@@ -33,6 +33,7 @@ unresolved.
|
||||
| [004](decisions/004-accessor-surface.md) | Accepted | Complete accessors: `for_tcp_tls()` adopted; server borrows, client consumes |
|
||||
| [005](decisions/005-config-types-move-into-alktls.md) | Accepted | Identity + credentials + fingerprint types move into alktls; auth layer stays out |
|
||||
| [006](decisions/006-module-layout-and-tests.md) | Accepted | Eight-module layout; seed tests + integration invariant pins |
|
||||
| [007](decisions/007-cert-type-negotiation.md) | Accepted | RFC 7250 cert-type negotiation: the offer follows the identity (deviation from alknet; resolves OQ-TLS-10) |
|
||||
|
||||
## Lifecycle
|
||||
|
||||
|
||||
+24
-11
@@ -48,7 +48,7 @@ exactly the two inputs the client config consumes:
|
||||
|
||||
| Local identity | Presented |
|
||||
|----------------|-----------|
|
||||
| `RawKey` | RFC 7250 raw public key (SPKI DER; `only_raw_public_keys()` auto-detected from the DER) |
|
||||
| `RawKey` | the Ed25519 SPKI as the client cert, under the X.509 offer (ADR-007 — the cert-type extension is an offer format, not an identity statement; the server extracts the `ed25519:` fingerprint either way) |
|
||||
| `X509` | the cert chain + key, loaded from disk |
|
||||
| `SelfSigned` / `None` | nothing (`NoClientCertResolver`) — documented, resolved in OQ-TLS-02 |
|
||||
| `Acme` | **config error** (`TlsError::AcmeConfig`) — server-only identity |
|
||||
@@ -82,15 +82,21 @@ 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.
|
||||
**RFC 7250 over TCP is the crate's own composition now** (ADR-007,
|
||||
resolving OQ-TLS-10): `FingerprintPinVerifier` derives its cert-type
|
||||
offer from the pin format — an `ed25519:<hex>` pin offers
|
||||
`server_certificate_types = [RawPublicKey]` and completes against a
|
||||
crate-built raw-key server (`raw_key_server_path_completes_with_crate_pin_client`);
|
||||
a `SHA256:<hex>` pin keeps the X.509 offer. A raw-key *client*
|
||||
identity presents its SPKI under the X.509 offer and
|
||||
`AcceptAnyCertVerifier` accepts it end-to-end
|
||||
(`raw_key_client_presents_spki_and_server_extracts_fingerprint`). A
|
||||
pin-format/cert-kind mismatch fails closed at negotiation, never a
|
||||
downgrade (`ed25519_pin_against_x509_server_fails_closed_at_negotiation`).
|
||||
Raw-key peers that ride iroh/noq use those transports' own TLS and are
|
||||
unaffected. The strict-server limit (a foreign server demanding
|
||||
raw-only client certs still rejects an X.509-offer presentation) is
|
||||
recorded in ADR-007 §Limits.
|
||||
|
||||
## `FingerprintPinVerifier`
|
||||
|
||||
@@ -109,6 +115,12 @@ key. This verifier checks proof-of-possession; the server-side
|
||||
`AcceptAnyCertVerifier` does not (see
|
||||
[server.md](server.md), OQ-TLS-09).
|
||||
|
||||
The cert-type offer follows the pin format (ADR-007):
|
||||
`requires_raw_public_keys()` returns `true` for `ed25519:` pins
|
||||
(the peer presents an RFC 7250 raw key), `false` for `SHA256:` pins
|
||||
(the peer presents an X.509 cert) — a mismatched pairing fails closed
|
||||
at negotiation.
|
||||
|
||||
## The root-store fallback (alknet ADR-088 §5)
|
||||
|
||||
The `None` + X.509 CA path loads the platform's native root certs
|
||||
@@ -148,4 +160,5 @@ X.509 endpoint needs it regardless of transport.
|
||||
- alknet ADR-034 (verifier selection), ADR-088 §5 (root-store
|
||||
fallback), ADR-091 (`ConnectionCredentials`)
|
||||
- ADR-002 (`TlsError`), ADR-004 (accessors), ADR-005 (credential
|
||||
types), ADR-006 (tests — the verifier-selection matrix)
|
||||
types), ADR-006 (tests — the verifier-selection matrix), ADR-007
|
||||
(the cert-type negotiation / offer-follows-identity rule)
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
status: accepted
|
||||
last_updated: 2026-09-11
|
||||
---
|
||||
|
||||
# ADR-007: RFC 7250 cert-type negotiation — the offer follows the identity (deviation from alknet)
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (2026-09-11). Resolves OQ-TLS-10.
|
||||
|
||||
## Context
|
||||
|
||||
Review 001 §U-3's executed handshake suites found that the extracted
|
||||
code could not negotiate RFC 7250 (raw public key) peers over
|
||||
rustls-driven TCP+TLS at all:
|
||||
|
||||
- The crate's `FingerprintPinVerifier` kept rustls' trait-default
|
||||
`requires_raw_public_keys() == false`, so a client pinning an
|
||||
`ed25519:<hex>` fingerprint never offered
|
||||
`server_certificate_types = [RawPublicKey]` — the raw-key server
|
||||
(whose resolver has `only_raw_public_keys() == true`) requires that
|
||||
offer and aborted every handshake with `HandshakeFailure`.
|
||||
- A raw-key *client* identity (`RawKeyClientCertResolver` with
|
||||
`only_raw_public_keys() == true`) made rustls offer
|
||||
`client_certificate_types = [RawPublicKey]` (rustls 0.23.44 sends
|
||||
that one-element list iff the resolver's
|
||||
`only_raw_public_keys()` is true — never a mixed list), which the
|
||||
crate's `AcceptAnyCertVerifier` (`requires_raw_public_keys() ==
|
||||
false`) rejected with `IncorrectCertificateTypeExtension`.
|
||||
|
||||
Mechanism verified empirically (duplex-pair handshakes) 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`. Recorded as OQ-TLS-10, initially
|
||||
**open** with a "the gap is inherited from alknet, behavior
|
||||
preservation holds" framing and options (a) document-the-gap /
|
||||
(b) pin-format-driven offer / (c) raw-key-only server verifier,
|
||||
deferred as "API-shape decisions before the first consumer".
|
||||
|
||||
That framing was wrong in two ways:
|
||||
|
||||
1. **The gap contradicts the inherited spec, not just an ideal.**
|
||||
alknet ADR-082 states "the raw-key path (RFC 7250) works for both
|
||||
QUIC and TCP+TLS", and the alknet `crates/tls` README lists
|
||||
TCP+TLS as the raw-key identity's fallback transport. The alknet
|
||||
*code* never delivered this (no `requires_raw_public_keys()`
|
||||
override exists anywhere in alknet-tls; raw-key traffic rode iroh's
|
||||
own TLS, which overrides the knob on both verifier sides). The
|
||||
extraction faithfully ported the bug. Behavior-preservation in this
|
||||
crate means the load-bearing TLS postures (0-RTT, provider,
|
||||
nine-scheme list, fail-closed, root-store fallback) — not
|
||||
"preserve defects the prior art's spec already forbids".
|
||||
2. **The deferral rationale was circular.** "Wait for the first
|
||||
raw-key-over-TCP consumer" — but this crate is the component that
|
||||
must *enable* that consumer; a consumer cannot appear first. And
|
||||
the fix required no API change at all (a trait override + one
|
||||
boolean; every public signature unchanged).
|
||||
|
||||
## Decision
|
||||
|
||||
The cert-type offer derives from the configured identity, per
|
||||
connection. Two changes, both in `src/client.rs`, no API-shape change:
|
||||
|
||||
1. **`FingerprintPinVerifier::requires_raw_public_keys()`** is
|
||||
overridden to derive from the pin format: `ed25519:<hex>` → `true`
|
||||
(offer `[RawPublicKey]` — the known peer presents an RFC 7250 raw
|
||||
key), `SHA256:<hex>` → `false` (the default X.509 offer). This is
|
||||
iroh's shape generalized: iroh's verifiers hardcode raw keys for
|
||||
its NodeId identity; here the *pin format* — the same string that
|
||||
selects the verification algorithm — also selects the offer
|
||||
format, so a peer's identity kind cannot be mis-negotiated.
|
||||
2. **`RawKeyClientCertResolver` presents the SPKI under the X.509
|
||||
offer** (`only_raw_public_keys() == false` unconditionally). rustls
|
||||
sends `client_certificate_types = [RawPublicKey]` only when the
|
||||
resolver demands raw-only, and a raw-only client offer can only
|
||||
negotiate against a server verifier with
|
||||
`requires_raw_public_keys() == true` — the crate's
|
||||
request-but-don't-require `AcceptAnyCertVerifier` correctly keeps
|
||||
`false` (it accepts both cert types and must not demand raw keys
|
||||
from X.509 clients). Under the X.509 offer the SPKI DER goes out as
|
||||
opaque cert bytes; the server-side fingerprint extraction
|
||||
(`fingerprint_from_cert_der`) reads the Ed25519 SPKI either way,
|
||||
and the CertificateVerify is signed by the real Ed25519 key. The
|
||||
extension is a transport-level offer format, not an identity
|
||||
statement — the identity is what is presented and how it verifies.
|
||||
|
||||
`AcceptAnyCertVerifier` stays on the trait default (`false`) — it
|
||||
accepts both cert types, which is the request-but-don't-require shape
|
||||
(N-4's correction in review 001 stands).
|
||||
|
||||
## Why this is recorded as a deviation, not behavior preservation
|
||||
|
||||
alknet's code never negotiated raw-key over TCP — but the deviation is
|
||||
from the **extracted code's executed behavior**, restoring what the
|
||||
inherited **spec** (alknet ADR-082, the `crates/tls` README) always
|
||||
promised and what the crate's purpose (`TlsIdentity::RawKey` exists as
|
||||
a variant) implies. Per AGENTS.md convention 10, deviations are
|
||||
recorded rather than silent; this ADR is the record.
|
||||
|
||||
## Behavior-preservation invariants under the change
|
||||
|
||||
- **Fail-closed, preserved and strengthened.** Unknown raw-key remote
|
||||
+ CA path: unchanged (`HandshakeFailure`, suite 2). New and
|
||||
deliberate: an `ed25519:` pin against an X.509 server now aborts at
|
||||
cert-type negotiation (suite 2b) — previously it failed later at pin
|
||||
verification. Same verdict, earlier, no downgrade path in either
|
||||
direction; the pin format is a promise about the peer's cert kind
|
||||
and a mismatch is a configuration error, not something to paper
|
||||
over. A `SHA256:` pin against a raw-key server likewise fails at
|
||||
negotiation (the client never offers the raw-key server cert type
|
||||
the raw-key resolver requires).
|
||||
- **No cross-pin interference.** The two pin formats cannot share one
|
||||
*config's* offer — but they never needed to: verifier selection is
|
||||
per-connection-credentials (ADR-004/ADR-005), each dial builds its
|
||||
config from its own `remote_identity`. The option-(b) objection in
|
||||
OQ-TLS-10 ("the two pin formats cannot share one client config")
|
||||
described a configuration that does not exist in the API.
|
||||
- **0-RTT, provider, nine-scheme list, root-store fallback, ACME
|
||||
append**: untouched by this change.
|
||||
- **QUIC parity.** The cert-type extension is a ClientHello-level
|
||||
mechanism; `for_noq()` wraps the same `rustls::ClientConfig`, so
|
||||
both transports negotiate identically. (iroh remains its own TLS
|
||||
stack, outside this crate.)
|
||||
|
||||
## Limits (honest scope)
|
||||
|
||||
The SPKI-under-X.509 presentation makes a raw-key *client* presentable
|
||||
to **this crate's** `AcceptAnyCertVerifier` (and to any permissive
|
||||
verifier). A foreign strict RFC 7250 server that *demands*
|
||||
`client_certificate_types = [RawPublicKey]` (`requires_raw_public_keys() == true` server-side) will still reject an X.509-offer
|
||||
presentation. If the rewrite needs that, the additive route is a
|
||||
raw-key-only server-verifier sibling (OQ-TLS-10 option (c)), which
|
||||
pairs with OQ-TLS-09's verify-presenting verifier (option (b)) — both
|
||||
remain additive and are not needed for the crate's own compositions.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- The crate's own compositions now work: pin client ↔ raw-key server,
|
||||
raw-key client ↔ crate server — the interop `TlsIdentity::RawKey`
|
||||
implies, on both transports (tcp feature and noq).
|
||||
- No API change: every public signature, type, and feature gate is
|
||||
unchanged; consumers see only working handshakes.
|
||||
- The pin-format-driven offer is self-consistent: the same string
|
||||
selects the verifier algorithm and the cert-type offer.
|
||||
|
||||
**Negative:**
|
||||
|
||||
- A misconfigured `ed25519:` pin against an X.509 peer (or vice versa)
|
||||
fails one step earlier with a negotiation alert rather than a pin
|
||||
mismatch — operators must match the pin format to the peer's actual
|
||||
cert kind (which they must do anyway for verification to pass).
|
||||
- The resolver type name (`RawKeyClientCertResolver`) is now
|
||||
slightly historical — it serves both identity kinds' cert material.
|
||||
Renaming is free before the first consumer; deferred as cosmetic.
|
||||
|
||||
## References
|
||||
|
||||
- OQ-TLS-10 (`docs/architecture/open-questions.md`) — resolved by
|
||||
this ADR
|
||||
- `tests/handshake_behavior.rs` — the executed pins, including the
|
||||
crate-native suite 3 and the new negotiation fail-closed suite
|
||||
- `tests/invariant_pins.rs` — the resolver-offer pins (updated)
|
||||
- review 001 §U-3, §N-4 (with its correction) — the discovery chain
|
||||
- `tasks/handshake-tests.md` — the task whose premises exposed the gap
|
||||
- alknet ADR-082 ("works for both QUIC and TCP+TLS" — the spec
|
||||
promise), alknet ADR-034, ADR-001 (inheritance + deviations rule)
|
||||
- iroh `iroh/src/tls/verifier.rs` — the working prior art for
|
||||
raw-key verifiers
|
||||
- rustls 0.23.44 `server/hs.rs::process_cert_type_extension`,
|
||||
`client/hs.rs::process_cert_type_extension` — the negotiation
|
||||
mechanism
|
||||
@@ -22,7 +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 |
|
||||
| OQ-TLS-10 | RFC 7250 over TCP: cert-type negotiation gap | **resolved** (ADR-007) | high |
|
||||
|
||||
## Identity & types
|
||||
|
||||
@@ -156,52 +156,24 @@ are authoritative; the Phase 0 doc's statuses are the historical record.
|
||||
(`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),
|
||||
- **Status**: resolved (2026-09-11) — by deviation from alknet,
|
||||
[ADR-007](decisions/007-cert-type-negotiation.md). Option (b) plus a
|
||||
resolver change: `FingerprintPinVerifier::requires_raw_public_keys()`
|
||||
derives from the pin format (`ed25519:` → `true`, `SHA256:` →
|
||||
`false`), and `RawKeyClientCertResolver` presents the SPKI under the
|
||||
X.509 offer (`only_raw_public_keys() == false`). The gap was a bug in
|
||||
the prior art (alknet's code never delivered its spec's raw-key-over-
|
||||
TCP promise), not an alknet behavior to preserve — so the
|
||||
behavior-preservation invariant does not cover it. The two pin
|
||||
formats negotiate independently (per-connection verifier, never
|
||||
shared); fail-closed is preserved and strengthened (an `ed25519:`
|
||||
pin against an X.509 server aborts at negotiation, never a
|
||||
downgrade). The original "defer until the first consumer" rationale
|
||||
was circular — the crate is the component that must enable the
|
||||
raw-key-over-TCP consumer, and the fix required no API change.
|
||||
- **Cross-references**: src/client.rs (`FingerprintPinVerifier`,
|
||||
`RawKeyClientCertResolver`), src/server.rs (`RawKeyCertResolver`,
|
||||
`AcceptAnyCertVerifier`), review 001 §N-4, ADR-007,
|
||||
iroh `iroh/src/tls/verifier.rs` (the working prior art)
|
||||
|
||||
## Quality / process
|
||||
@@ -236,8 +208,3 @@ 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)).
|
||||
- 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)).
|
||||
@@ -78,6 +78,7 @@ All design decisions are documented as ADRs in [decisions/](decisions/).
|
||||
| [004](decisions/004-accessor-surface.md) | Complete the accessors | `for_tcp_tls()` adopted; server accessors borrow (`&self`), client accessors consume |
|
||||
| [005](decisions/005-config-types-move-into-alktls.md) | Config types move into alktls | Identity + credentials + fingerprint move in; auth layer stays out |
|
||||
| [006](decisions/006-module-layout-and-tests.md) | Module layout and test surface | Eight modules; in-module seed tests + integration invariant pins |
|
||||
| [007](decisions/007-cert-type-negotiation.md) | RFC 7250 cert-type negotiation | The cert-type offer follows the identity/pin format — raw-key-over-TCP works (deviation from alknet; resolves OQ-TLS-10) |
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
@@ -108,17 +108,21 @@ 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
|
||||
**Interop note (ADR-007, 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).
|
||||
the client to offer `[RawPublicKey]` *server* cert types, which rustls
|
||||
sends only when the client verifier overrides
|
||||
`requires_raw_public_keys() == true`. The crate's own
|
||||
`FingerprintPinVerifier` now does exactly that for `ed25519:` pins
|
||||
(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`).
|
||||
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.
|
||||
their negotiation).
|
||||
|
||||
## The ACME path
|
||||
|
||||
|
||||
+46
-20
@@ -63,11 +63,12 @@ impl TlsClientConfig {
|
||||
}
|
||||
|
||||
/// Build the client-auth cert resolver that presents the local node's TLS
|
||||
/// identity. For `TlsIdentity::RawKey` the Ed25519 key is presented as an RFC
|
||||
/// 7250 raw public key client cert (`only_raw_public_keys() == true`) — the
|
||||
/// client-side equivalent of the server's `RawKeyCertResolver`. For X.509 the
|
||||
/// cert chain + key are loaded from disk. `None` (no `local_identity` configured)
|
||||
/// resolves to no client cert (the server gets nothing to fingerprint).
|
||||
/// identity. For `TlsIdentity::RawKey` the Ed25519 SPKI is presented as the
|
||||
/// client cert (opaque bytes under the X.509 offer — see
|
||||
/// `RawKeyClientCertResolver`'s docs for the negotiation note, ADR-007).
|
||||
/// For X.509 the cert chain + key are loaded from disk. `None` (no
|
||||
/// `local_identity` configured) resolves to no client cert (the server gets
|
||||
/// nothing to fingerprint).
|
||||
pub fn build_client_auth(
|
||||
provider: &Arc<rustls::crypto::CryptoProvider>,
|
||||
tls_identity: &Option<TlsIdentity>,
|
||||
@@ -149,9 +150,24 @@ pub fn load_platform_root_cert_store() -> Result<rustls::RootCertStore, TlsError
|
||||
Ok(roots)
|
||||
}
|
||||
|
||||
/// Client cert resolver that presents a single RFC 7250 raw public key (or
|
||||
/// X.509 cert chain). For raw keys `only_raw_public_keys()` returns `true` so
|
||||
/// rustls negotiates the RFC 7250 ClientCertificateType extension.
|
||||
/// Client cert resolver that presents the local identity's cert material:
|
||||
/// an RFC 7250 raw public key SPKI (for `RawKey`) or an X.509 cert chain.
|
||||
/// Both go out under rustls' X.509 offer (`only_raw_public_keys() == false`)
|
||||
/// — see the negotiation note below.
|
||||
///
|
||||
/// # Client-cert-type negotiation (ADR-007)
|
||||
///
|
||||
/// rustls offers `client_certificate_types = [RawPublicKey]` iff this
|
||||
/// resolver's `only_raw_public_keys()` is `true`. `AcceptAnyCertVerifier`
|
||||
/// (the crate's server verifier) keeps `requires_raw_public_keys() == false`,
|
||||
/// which rejects a raw-only offer (`IncorrectCertificateTypeExtension` —
|
||||
/// server/hs.rs's `(false, true, false)` arm). The SPKI presentation
|
||||
/// therefore goes out under the X.509 offer with the SPKI DER as the cert
|
||||
/// bytes: the extension is a transport-level offer format, not an identity
|
||||
/// statement, and the server-side fingerprint extraction
|
||||
/// (`fingerprint_from_cert_der`) reads the Ed25519 SPKI either way. Pinned
|
||||
/// end-to-end by `tests/handshake_behavior.rs`
|
||||
/// (`raw_key_client_presents_spki_and_server_extracts_fingerprint`).
|
||||
pub struct RawKeyClientCertResolver {
|
||||
key: Arc<rustls::sign::CertifiedKey>,
|
||||
raw_public_keys: bool,
|
||||
@@ -159,18 +175,13 @@ pub struct RawKeyClientCertResolver {
|
||||
|
||||
impl RawKeyClientCertResolver {
|
||||
pub fn new(key: Arc<rustls::sign::CertifiedKey>) -> Self {
|
||||
let raw_public_keys = key.cert.len() == 1 && is_ed25519_spki(&key.cert[0]);
|
||||
Self {
|
||||
key,
|
||||
raw_public_keys,
|
||||
raw_public_keys: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ed25519_spki(cert_der: &rustls::pki_types::CertificateDer<'_>) -> bool {
|
||||
extract_ed25519_raw_key_from_spki(cert_der.as_ref()).is_some()
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RawKeyClientCertResolver {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RawKeyClientCertResolver")
|
||||
@@ -328,6 +339,20 @@ impl rustls::client::danger::ServerCertVerifier for FingerprintPinVerifier {
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
self.supported.supported_schemes()
|
||||
}
|
||||
|
||||
/// The pin format decides the server cert-type offer (ADR-007):
|
||||
/// an `ed25519:<hex>` pin means the known peer presents an RFC 7250
|
||||
/// raw public key, so the client must offer
|
||||
/// `server_certificate_types = [RawPublicKey]` — which rustls sends
|
||||
/// only when this returns `true`. A `SHA256:<hex>` pin means the
|
||||
/// known peer presents an X.509 cert, so this stays `false` (the
|
||||
/// default X.509 offer). Either way the offer follows the pin —
|
||||
/// a raw-key server is reachable, an X.509 server pinned by
|
||||
/// `SHA256:` negotiates unchanged, and a mismatched pairing fails
|
||||
/// closed at negotiation (never downgrades).
|
||||
fn requires_raw_public_keys(&self) -> bool {
|
||||
self.fingerprint.starts_with("ed25519:")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -537,14 +562,15 @@ mod tests {
|
||||
"build_client_auth must build a resolver for a RawKey identity"
|
||||
);
|
||||
let resolver = resolver.unwrap();
|
||||
assert!(
|
||||
resolver.only_raw_public_keys(),
|
||||
"RawKey client auth resolver must present raw public keys (RFC 7250)"
|
||||
);
|
||||
assert!(
|
||||
resolver.has_certs(),
|
||||
"RawKey client auth resolver must report it has a cert to present"
|
||||
);
|
||||
assert!(
|
||||
!resolver.only_raw_public_keys(),
|
||||
"the SPKI presentation goes out under the X.509 offer (ADR-007: \
|
||||
a raw-only client cert type is rejected by AcceptAnyCertVerifier)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -745,8 +771,8 @@ mod tests {
|
||||
match label {
|
||||
"raw-key" => {
|
||||
assert!(
|
||||
resolver.has_certs() && resolver.only_raw_public_keys(),
|
||||
"RawKey must present a raw public key (RFC 7250)"
|
||||
resolver.has_certs() && !resolver.only_raw_public_keys(),
|
||||
"RawKey must present its SPKI under the X.509 offer (ADR-007)"
|
||||
);
|
||||
}
|
||||
"x509" => {
|
||||
|
||||
@@ -82,6 +82,29 @@ The four handshake-level gaps (review 001 §U-3):
|
||||
stays inside it)
|
||||
- tasks/integration-suite.md (the existing suite this extends)
|
||||
|
||||
## Postscript (2026-09-11 — the OQ-TLS-10 resolution)
|
||||
|
||||
The open gap above is now **resolved by ADR-007** (deviation from
|
||||
alknet; OQ-TLS-10 → resolved). The two gap premises this task pinned
|
||||
are no longer true:
|
||||
|
||||
- Suite 1's original premise (crate pin client ↔ raw-key server → ok)
|
||||
is now **true**: `FingerprintPinVerifier::requires_raw_public_keys()`
|
||||
derives from the pin format (`ed25519:` → `true`), so the crate's
|
||||
own pin client completes against the crate's raw-key server —
|
||||
`raw_key_server_path_completes_with_crate_pin_client` (no custom
|
||||
verifier needed anymore).
|
||||
- Suite 3b's premise is now **inverted**: a raw-key client identity
|
||||
presents its SPKI under the X.509 offer and
|
||||
`AcceptAnyCertVerifier` accepts it end-to-end
|
||||
(`raw_key_client_presents_spki_and_server_extracts_fingerprint`).
|
||||
- New pin: `ed25519_pin_against_x509_server_fails_closed_at_negotiation`
|
||||
— a pin-format/cert-kind mismatch aborts at negotiation, never a
|
||||
downgrade.
|
||||
|
||||
Rationale and limits (incl. the strict-foreign-server caveat) in
|
||||
ADR-007; client.md/server.md notes flipped accordingly.
|
||||
|
||||
## Notes
|
||||
|
||||
> **Major finding during implementation (recorded as OQ-TLS-10):** the
|
||||
|
||||
+80
-133
@@ -4,32 +4,29 @@
|
||||
//! `tokio-rustls` drives real rustls handshakes; no external transport
|
||||
//! is involved (ADR-006's boundary is not crossed).
|
||||
//!
|
||||
//! Verified against rustls 0.23.44's cert-type negotiation
|
||||
//! (`server/hs.rs::process_cert_type_extension`,
|
||||
//! `client/hs.rs::process_cert_type_extension`) and pinned here:
|
||||
//! Cert-type negotiation is resolved by ADR-007 (OQ-TLS-10):
|
||||
//!
|
||||
//! - An X.509 server negotiates with any client not offering a raw-key
|
||||
//! client resolver; the fingerprint pin executes on the presented cert.
|
||||
//! - A raw-key server (resolver `only_raw_public_keys() == true`)
|
||||
//! requires the client to offer `[RawPublicKey]` *server* cert types,
|
||||
//! which rustls sends only when the client verifier overrides
|
||||
//! `requires_raw_public_keys() == true` (the iroh prior-art shape,
|
||||
//! `iroh/src/tls/verifier.rs`). This crate's `FingerprintPinVerifier`
|
||||
//! keeps the trait default `false`, so a crate-built pin client
|
||||
//! cannot reach a crate-built raw-key server — the handshake fails
|
||||
//! closed (`HandshakeFailure`). Recorded as OQ-TLS-10.
|
||||
//! - A raw-key *client* resolver offers `[RawPublicKey]` client cert
|
||||
//! types, which `AcceptAnyCertVerifier` (`requires_raw_public_keys()
|
||||
//! == false`) rejects — N-4's interop trap, executed.
|
||||
//! - `FingerprintPinVerifier` offers `server_certificate_types =
|
||||
//! [RawPublicKey]` iff the pin is `ed25519:` — the crate's own pin
|
||||
//! client completes against a crate-built raw-key server, and an
|
||||
//! `ed25519:` pin against an X.509 server fails closed at
|
||||
//! negotiation (a cert-type mismatch can never downgrade to
|
||||
//! verification).
|
||||
//! - A raw-key *client* identity presents its SPKI under the X.509
|
||||
//! offer (`only_raw_public_keys() == false` — the extension is an
|
||||
//! offer format, not an identity statement); `AcceptAnyCertVerifier`
|
||||
//! accepts it and the server extracts the `ed25519:` fingerprint.
|
||||
//!
|
||||
//! Mechanism reference: rustls 0.23.44's cert-type negotiation
|
||||
//! (`server/hs.rs::process_cert_type_extension`,
|
||||
//! `client/hs.rs::process_cert_type_extension`).
|
||||
|
||||
#![cfg(feature = "tcp")]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier};
|
||||
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||
use rustls::DigitallySignedStruct;
|
||||
use rustls::pki_types::{CertificateDer, ServerName};
|
||||
use tokio::io::duplex;
|
||||
|
||||
use alktls::fingerprint_from_cert_der;
|
||||
@@ -196,82 +193,46 @@ async fn unknown_raw_key_remote_fails_closed() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Suite 3 — the RFC 7250 raw-key server path executed end-to-end: the
|
||||
/// server presents its SPKI, the client pins the `ed25519:<hex>`
|
||||
/// fingerprint and completes — with the one verifier shape rustls'
|
||||
/// negotiation requires on the client side
|
||||
/// (`requires_raw_public_keys() == true`; iroh's prior art,
|
||||
/// `iroh/src/tls/verifier.rs`). The crate's own `FingerprintPinVerifier`
|
||||
/// cannot negotiate this path (no override — OQ-TLS-10), so the custom
|
||||
/// verifier here is the shape any raw-key-over-TCP consumer must bring.
|
||||
/// Suite 2b — pin-format mismatch fails closed at cert-type negotiation
|
||||
/// (ADR-007): an `ed25519:` pin makes the client offer raw-key-only
|
||||
/// server cert types; the X.509 server's presentation cannot satisfy
|
||||
/// that offer, so rustls aborts with HandshakeFailure *before* the pin
|
||||
/// verifier ever runs — never a downgrade to CA verification or a
|
||||
/// silent fallback to the X.509 offer.
|
||||
#[tokio::test]
|
||||
async fn raw_key_server_path_completes_with_requires_raw_verifier() {
|
||||
#[derive(Debug)]
|
||||
struct RequiresRawPinVerifier {
|
||||
fingerprint: String,
|
||||
supported: rustls::crypto::WebPkiSupportedAlgorithms,
|
||||
presented: Arc<std::sync::Mutex<Option<Vec<u8>>>>,
|
||||
}
|
||||
async fn ed25519_pin_against_x509_server_fails_closed_at_negotiation() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let (cert_path, key_path, _) = write_x509_pair(dir.path());
|
||||
|
||||
impl ServerCertVerifier for RequiresRawPinVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
|
||||
let presented =
|
||||
fingerprint_from_cert_der(end_entity.as_ref()).ok_or(rustls::Error::General(
|
||||
"raw-key pin: failed to fingerprint the presented cert".to_string(),
|
||||
))?;
|
||||
if presented == self.fingerprint {
|
||||
*self.presented.lock().unwrap_or_else(|e| e.into_inner()) =
|
||||
Some(end_entity.as_ref().to_vec());
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
} else {
|
||||
Err(rustls::Error::General(format!(
|
||||
"raw-key pin mismatch: expected {} got {}",
|
||||
self.fingerprint, presented
|
||||
)))
|
||||
}
|
||||
}
|
||||
let acceptor = server_config(&TlsIdentity::X509 {
|
||||
cert: cert_path,
|
||||
key: key_path,
|
||||
})
|
||||
.await
|
||||
.for_tcp_tls();
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Err(rustls::Error::General(
|
||||
"raw-key pin: TLS 1.2 not exercised on this path".to_string(),
|
||||
))
|
||||
}
|
||||
let credentials = ConnectionCredentials::new().with_remote_identity(RemoteIdentity {
|
||||
fingerprint: "ed25519:0000000000000000000000000000000000000000000000000000000000000000"
|
||||
.to_string(),
|
||||
});
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
rustls::crypto::verify_tls13_signature_with_raw_key(
|
||||
message,
|
||||
&rustls::pki_types::SubjectPublicKeyInfoDer::from(cert.as_ref().to_vec()),
|
||||
dss,
|
||||
&self.supported,
|
||||
)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
self.supported.supported_schemes()
|
||||
}
|
||||
|
||||
fn requires_raw_public_keys(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
let result = round_trip(acceptor, connector(&credentials)).await;
|
||||
let err = result.expect_err("an ed25519 pin against an X.509 server must fail closed");
|
||||
assert!(
|
||||
err.contains("HandshakeFailure"),
|
||||
"the cert-type negotiation abort must surface as a handshake alert, got: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Suite 3 — the RFC 7250 raw-key server path end-to-end, entirely on
|
||||
/// the crate's public API (ADR-007): the server presents its SPKI, the
|
||||
/// client pins the `ed25519:<hex>` fingerprint with the crate's own
|
||||
/// `FingerprintPinVerifier` (whose `requires_raw_public_keys()` now
|
||||
/// derives from the pin format), and the handshake completes with the
|
||||
/// raw-key cert type negotiated — server cert verified as the SPKI
|
||||
/// carrying the raw Ed25519 public key.
|
||||
#[tokio::test]
|
||||
async fn raw_key_server_path_completes_with_crate_pin_client() {
|
||||
let server_key = Ed25519SecretKey::generate();
|
||||
let server_public: [u8; 32] = server_key.public().to_bytes();
|
||||
let server_spki =
|
||||
@@ -284,69 +245,55 @@ async fn raw_key_server_path_completes_with_requires_raw_verifier() {
|
||||
.await
|
||||
.for_tcp_tls();
|
||||
|
||||
let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
|
||||
let presented = Arc::new(std::sync::Mutex::new(None));
|
||||
let verifier = RequiresRawPinVerifier {
|
||||
let credentials = ConnectionCredentials::new().with_remote_identity(RemoteIdentity {
|
||||
fingerprint: server_fp,
|
||||
supported: provider.signature_verification_algorithms,
|
||||
presented: Arc::clone(&presented),
|
||||
};
|
||||
let mut config = rustls::ClientConfig::builder_with_provider(provider)
|
||||
.with_safe_default_protocol_versions()
|
||||
.expect("protocol versions")
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||
.with_client_cert_resolver(Arc::new(alktls::NoClientCertResolver));
|
||||
config.alpn_protocols = vec![ALPN.to_vec()];
|
||||
let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
|
||||
});
|
||||
|
||||
let server_seen = round_trip(acceptor, connector)
|
||||
let server_seen = round_trip(acceptor, connector(&credentials))
|
||||
.await
|
||||
.expect("the RFC 7250 raw-key handshake must complete");
|
||||
.expect("the crate pin client must complete the RFC 7250 raw-key handshake (ADR-007)");
|
||||
|
||||
assert!(
|
||||
server_seen.is_none(),
|
||||
"no client cert was presented; the server must see none, got: {server_seen:?}"
|
||||
);
|
||||
|
||||
let presented = presented.lock().unwrap_or_else(|e| e.into_inner()).clone();
|
||||
let presented = presented.expect("the verifier must have seen the server cert");
|
||||
assert_eq!(
|
||||
alktls::extract_ed25519_raw_key_from_spki(&presented),
|
||||
Some(server_public),
|
||||
"the server's presented cert must be the RFC 7250 SPKI carrying the \
|
||||
raw Ed25519 public key"
|
||||
);
|
||||
}
|
||||
|
||||
/// Suite 3b — N-4's interop trap, executed: a crate-built raw-key *client*
|
||||
/// resolver offers `client_certificate_types = [RawPublicKey]` only, which
|
||||
/// `AcceptAnyCertVerifier` (`requires_raw_public_keys() == false`) rejects
|
||||
/// with `IncorrectCertificateTypeExtension` → HandshakeFailure. A raw-key
|
||||
/// client identity cannot present itself to this crate's own server today
|
||||
/// (OQ-TLS-10). Pinned so any verifier change flags it.
|
||||
/// Suite 3b — N-4's interop trap, resolved (ADR-007): a raw-key client
|
||||
/// identity presents its SPKI under the X.509 offer
|
||||
/// (`only_raw_public_keys() == false` — the extension is an offer format,
|
||||
/// not an identity statement), which `AcceptAnyCertVerifier` accepts; the
|
||||
/// server extracts the client's `ed25519:` fingerprint end-to-end.
|
||||
#[tokio::test]
|
||||
async fn raw_key_client_resolver_fails_against_accept_any_cert_verifier() {
|
||||
async fn raw_key_client_presents_spki_and_server_extracts_fingerprint() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let (cert_path, key_path, _) = write_x509_pair(dir.path());
|
||||
let (cert_path, key_path, cert_der) = write_x509_pair(dir.path());
|
||||
let server_fp = fingerprint_from_cert_der(cert_der.as_ref()).expect("server fp");
|
||||
|
||||
let acceptor = server_config(&TlsIdentity::X509 {
|
||||
cert: cert_path,
|
||||
key: key_path,
|
||||
cert: cert_path.clone(),
|
||||
key: key_path.clone(),
|
||||
})
|
||||
.await
|
||||
.for_tcp_tls();
|
||||
|
||||
let client_key = Ed25519SecretKey::generate();
|
||||
let client_public: [u8; 32] = client_key.public().to_bytes();
|
||||
let credentials = ConnectionCredentials::new()
|
||||
.with_local_identity(TlsIdentity::RawKey(Ed25519SecretKey::generate()));
|
||||
.with_local_identity(TlsIdentity::RawKey(client_key))
|
||||
.with_remote_identity(RemoteIdentity {
|
||||
fingerprint: server_fp,
|
||||
});
|
||||
|
||||
let result = round_trip(acceptor, connector(&credentials)).await;
|
||||
let err = result.expect_err(
|
||||
"a raw-key client resolver cannot negotiate client cert types with \
|
||||
AcceptAnyCertVerifier (N-4)",
|
||||
let server_seen = round_trip(acceptor, connector(&credentials)).await.expect(
|
||||
"a raw-key client identity must present its SPKI to the crate's \
|
||||
own server verifier (ADR-007)",
|
||||
);
|
||||
assert!(
|
||||
err.contains("HandshakeFailure"),
|
||||
"the cert-type negotiation failure must surface as a handshake alert, got: {err}"
|
||||
|
||||
let expected = format!("ed25519:{}", hex::encode(client_public));
|
||||
assert_eq!(
|
||||
server_seen.as_deref(),
|
||||
Some(expected.as_str()),
|
||||
"the server must extract the raw-key client's ed25519 fingerprint"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,8 +110,9 @@ fn client_auth_presentation_matrix_full_construction() {
|
||||
let resolver = &config.into_rustls_config().client_auth_cert_resolver;
|
||||
match *label {
|
||||
"raw-key" => assert!(
|
||||
resolver.has_certs() && resolver.only_raw_public_keys(),
|
||||
"RawKey must present an RFC 7250 raw public key"
|
||||
resolver.has_certs() && !resolver.only_raw_public_keys(),
|
||||
"RawKey presents its SPKI under the X.509 offer (ADR-007: the \
|
||||
extension is an offer format, not an identity statement)"
|
||||
),
|
||||
"x509" => assert!(
|
||||
resolver.has_certs() && !resolver.only_raw_public_keys(),
|
||||
@@ -155,7 +156,10 @@ fn selection_and_auth_helpers_agree_with_the_full_config() {
|
||||
let sk = Ed25519SecretKey::generate();
|
||||
let local = Some(TlsIdentity::RawKey(sk.clone()));
|
||||
let resolver = build_client_auth(&provider(), &local).expect("raw-key resolver builds");
|
||||
assert!(resolver.has_certs() && resolver.only_raw_public_keys());
|
||||
assert!(
|
||||
resolver.has_certs() && !resolver.only_raw_public_keys(),
|
||||
"the raw-key resolver presents its SPKI under the X.509 offer (ADR-007)"
|
||||
);
|
||||
|
||||
let remote = Some(RemoteIdentity {
|
||||
fingerprint: "ed25519:aa".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user