docs(architecture): sync specs to post-extraction state (phases 0-5)
The crate-extraction migration (phases 0-5) is complete in the code;
the specs still carried forward/migration framing ("was welded",
"after the refactor", "currently duplicated", "does not exist yet",
"What moves from X to Y" tables, "Implementation ordering") that
described the migration rather than the resulting state. Updated 10
spec files to describe the current state cleanly.
Spec/code mismatches fixed:
- core/README.md: a stale paragraph said CallCredentials "stays in
alknet-call" while ADR-091 Am. 2026-07-17 removed it. Now consistent.
- tls/README.md: TlsClientConfig API described a planned
ClientVerifierContext + for_tcp_tls(&self) + rustls_config(&self);
the actual code is new(&ConnectionCredentials, alpn) +
for_quinn(self) + into_rustls_config(self). Updated to match.
- client/README.md, call/client-and-adapters.md: ConnectionCredentials
field is tls_identity / with_tls_identity in the code, not
local_identity / with_local_identity. Updated the specs describing
the current API (ADR-091 body keeps local_identity as the decided
name).
- client/README.md: dial_iroh description said the local key is
"extracted from creds.local_identity" — the code uses the pre-built
iroh endpoint's key (set at with_iroh time) and reads only
creds.remote_identity for the NodeId. Fixed.
- overview.md: said core has "no quinn/iroh deps" — core keeps
quinn/iroh for Connection::from_quinn/from_iroh. Fixed.
- call/client-and-adapters.md: a /// doc-comment block and
pub struct RemoteIdentity were floating outside any code fence
(orphaned closing backticks). Fixed.
- tls/README.md: TlsError sketch shows the full ADR-088 6-variant
enum; the code has a simplified 3-variant enum. Added an
implementation note flagging the divergence; ADR-088 shape kept as
target.
- call/README.md: review note said "ADR-029 migration pending" (stale
— migration landed). Updated to reflect phase 5 completion (pure
protocol crate, no TLS/transport deps, verified against Cargo.toml).
Migration framing removed (present-state descriptions instead):
- tls/README.md: "What moves from" tables -> module-contents tables;
"Implementation ordering / greenfield" section removed; "after the
refactor" section -> "What AlknetEndpoint does"; references to
extraction-source files (alknet-core/src/endpoint.rs,
alknet-call/src/client/call_client.rs) replaced with current file
locations (alknet-tls/src/{server,client,pem,signing}.rs).
- endpoint/README.md: "was two things welded" framing removed;
"after the extraction" section -> "What alknet-core looks like".
- core/endpoint.md: "Historical summary" section removed; clean
deprecation pointer.
- README.md, overview.md, open-questions.md: dates + present-tense
cleanup.
This commit is contained in:
1 parent
ddc577cd3e
commit
c6eef730e4
10 files changed
+326
-381
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-16
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alknet Architecture
|
# Alknet Architecture
|
||||||
@@ -242,7 +242,7 @@ adapter location map is now consistent: all HTTP-backed adapters
|
|||||||
| [crates/vault/service.md](crates/vault/service.md) | stable | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
|
| [crates/vault/service.md](crates/vault/service.md) | stable | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
|
||||||
| [crates/vault/protocol.md](crates/vault/protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
|
| [crates/vault/protocol.md](crates/vault/protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
|
||||||
| [crates/hub/README.md](crates/hub/README.md) | draft | alknet-hub crate — composes a subset of three endpoint types (web/native/iroh — ADR-086), channels substrate (ADR-079 relay), worker registration flow (OQ-58), identity over transports, aggregated peer env, connection lifecycle, service discovery |
|
| [crates/hub/README.md](crates/hub/README.md) | draft | alknet-hub crate — composes a subset of three endpoint types (web/native/iroh — ADR-086), channels substrate (ADR-079 relay), worker registration flow (OQ-58), identity over transports, aggregated peer env, connection lifecycle, service discovery |
|
||||||
| [crates/tls/README.md](crates/tls/README.md) | reviewed | alknet-tls crate — shared TLS config (`TlsServerConfig` + `TlsClientConfig`) shared across quinn + TCP+TLS + iroh; one cert, one ACME state machine, N transports; split ALPN lists per endpoint type (ADR-086, resolves OQ-62); `FingerprintPinVerifier` moved here from `alknet-call` (ADR-089 §5); `webpki-roots` fallback for empty platform stores (ADR-088 §5); fixes cert-reuse welding in `alknet-core/endpoint.rs` (ADR-082) |
|
| [crates/tls/README.md](crates/tls/README.md) | reviewed | alknet-tls crate — shared TLS config (`TlsServerConfig` + `TlsClientConfig`) shared across quinn + TCP+TLS + iroh; one cert, one ACME state machine, N transports; split ALPN lists per endpoint type (ADR-086, resolves OQ-62); `FingerprintPinVerifier` in `alknet-tls` (ADR-089 §5); `webpki-roots` fallback for empty platform stores (ADR-088 §5); isolates cert-reuse from transport wrappers (ADR-082) |
|
||||||
| [crates/client/README.md](crates/client/README.md) | draft | alknet-client crate — the native client dial seam (`AlknetClient`), client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key) unified on `&ConnectionCredentials` (ADR-091); optional SOCKS5 proxy (ADR-090 — UDP ASSOCIATE for QUIC, CONNECT for TCP+TLS, force-relay-only + HTTP-to-SOCKS5 bridge for iroh; OQ-67 resolved); produces `Connection` for `CallClient`/`ChannelClient` take-over; `CallClient::connect`/`ChannelClient::connect_quic` removed (dial centralized here); `alknet/register` named (wire protocol deferred, OQ-66) |
|
| [crates/client/README.md](crates/client/README.md) | draft | alknet-client crate — the native client dial seam (`AlknetClient`), client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key) unified on `&ConnectionCredentials` (ADR-091); optional SOCKS5 proxy (ADR-090 — UDP ASSOCIATE for QUIC, CONNECT for TCP+TLS, force-relay-only + HTTP-to-SOCKS5 bridge for iroh; OQ-67 resolved); produces `Connection` for `CallClient`/`ChannelClient` take-over; `CallClient::connect`/`ChannelClient::connect_quic` removed (dial centralized here); `alknet/register` named (wire protocol deferred, OQ-66) |
|
||||||
| [crates/endpoint/README.md](crates/endpoint/README.md) | draft | alknet-endpoint crate — the server-side accept-loop runner (`AlknetEndpoint`), extracted from `alknet-core` (ADR-083 Am. 2026-07-15); takes pre-built transports via `with_quinn`/`with_iroh`/`with_tcp_tls`; public `dispatch` for SSH/WT; `EndpointError` removed (vestigial); handler crates no longer transitively link quinn/iroh |
|
| [crates/endpoint/README.md](crates/endpoint/README.md) | draft | alknet-endpoint crate — the server-side accept-loop runner (`AlknetEndpoint`), extracted from `alknet-core` (ADR-083 Am. 2026-07-15); takes pre-built transports via `with_quinn`/`with_iroh`/`with_tcp_tls`; public `dispatch` for SSH/WT; `EndpointError` removed (vestigial); handler crates no longer transitively link quinn/iroh |
|
||||||
| [crates/channels/README.md](crates/channels/README.md) | draft | alknet-channels crate — multiplexing proxy, 9-byte chunk format, N channels over one transport stream |
|
| [crates/channels/README.md](crates/channels/README.md) | draft | alknet-channels crate — multiplexing proxy, 9-byte chunk format, N channels over one transport stream |
|
||||||
@@ -343,7 +343,7 @@ adapter location map is now consistent: all HTTP-backed adapters
|
|||||||
| [084](decisions/084-aws-lc-rs-crypto-provider.md) | aws-lc-rs as the TLS Crypto Provider | Accepted |
|
| [084](decisions/084-aws-lc-rs-crypto-provider.md) | aws-lc-rs as the TLS Crypto Provider | Accepted |
|
||||||
| [085](decisions/085-workspace-scope-core-vs-consumer-repos.md) | Workspace Scope — Core vs. Consumer Repos | Accepted |
|
| [085](decisions/085-workspace-scope-core-vs-consumer-repos.md) | Workspace Scope — Core vs. Consumer Repos | Accepted |
|
||||||
| [086](decisions/086-endpoint-types-and-entry-points.md) | Endpoint Types and Entry Points | Accepted |
|
| [086](decisions/086-endpoint-types-and-entry-points.md) | Endpoint Types and Entry Points | Accepted |
|
||||||
| [087](decisions/087-tlsclientconfig-not-blocked-on-dial.md) | `TlsClientConfig` Not Blocked on Dial Seam | Accepted (§5 amended by ADR-089 — `FingerprintPinVerifier` moves to `alknet-tls`; `alknet-call` sheds TLS deps; input framing amended by ADR-091 — `ClientVerifierContext` derived from `ConnectionCredentials`, not `CallCredentials`) |
|
| [087](decisions/087-tlsclientconfig-not-blocked-on-dial.md) | `TlsClientConfig` Not Blocked on Dial Seam | Accepted (§5 amended by ADR-089 — `FingerprintPinVerifier` in `alknet-tls`; `alknet-call` sheds TLS deps; input framing amended by ADR-091 — `TlsClientConfig::new` takes `ConnectionCredentials`, not `CallCredentials`) |
|
||||||
| [088](decisions/088-tlserror-shape.md) | `TlsError` Shape — Single Enum, Owned by `alknet-tls` | Accepted (§5 added — `webpki-roots` fallback when platform store is empty; §7 references ADR-089 for handshake-error surfacing) |
|
| [088](decisions/088-tlserror-shape.md) | `TlsError` Shape — Single Enum, Owned by `alknet-tls` | Accepted (§5 added — `webpki-roots` fallback when platform store is empty; §7 references ADR-089 for handshake-error surfacing) |
|
||||||
| [089](decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — Native Client Dial Seam | Accepted (resolves OQ-55; `CallClient::connect` / `ChannelClient::connect_quic` removed; §3/§5 amended by ADR-091 — dial takes `ConnectionCredentials`, not `CallCredentials`; `CallCredentials` removed per ADR-091 Am. 2026-07-17; `FingerprintPinVerifier` moved to `alknet-tls`; `ClientError` removed; `alknet-call` sheds TLS deps) |
|
| [089](decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — Native Client Dial Seam | Accepted (resolves OQ-55; `CallClient::connect` / `ChannelClient::connect_quic` removed; §3/§5 amended by ADR-091 — dial takes `ConnectionCredentials`, not `CallCredentials`; `CallCredentials` removed per ADR-091 Am. 2026-07-17; `FingerprintPinVerifier` moved to `alknet-tls`; `ClientError` removed; `alknet-call` sheds TLS deps) |
|
||||||
| [090](decisions/090-client-dial-socks5-proxy-seam.md) | Client-Dial SOCKS5 Proxy Seam | Accepted (§5 amended 2026-07-16 — OQ-67 resolved: iroh force-relay-only + HTTP-to-SOCKS5 bridge) |
|
| [090](decisions/090-client-dial-socks5-proxy-seam.md) | Client-Dial SOCKS5 Proxy Seam | Accepted (§5 amended 2026-07-16 — OQ-67 resolved: iroh force-relay-only + HTTP-to-SOCKS5 bridge) |
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-09
|
last_updated: 2026-07-17
|
||||||
review: call/review-call passed 2026-06-23 — registry, protocol, ADR (005/012/014/015/016/017/022/023/024), security, and pattern-consistency checks all conformant; 159 unit/integration tests green; `cargo build`, `cargo clippy -- -D warnings`, `cargo fmt --check`, `cargo test` clean. Call-completion gap (ADR-017 client/adapter surface) addressed 2026-06-26; ADR-029 migration pending. Transport generalization sweep (ADR-064 supersedes ADR-005; ADR-065 `from_stream`) synced 2026-07-09.
|
review: call/review-call passed 2026-06-23 — registry, protocol, ADR (005/012/014/015/016/017/022/023/024), security, and pattern-consistency checks all conformant; 159 unit/integration tests green; `cargo build`, `cargo clippy -- -D warnings`, `cargo fmt --check`, `cargo test` clean. Call-completion gap (ADR-017 client/adapter surface) addressed 2026-06-26; ADR-029 migration landed. Transport generalization sweep (ADR-064 supersedes ADR-005; ADR-065 `from_stream`) synced 2026-07-09. Crate-extraction sweep (phases 0–5) landed 2026-07-17: `ConnectionCredentials`/`RemoteIdentity` in `alknet-core` (ADR-091), `CallCredentials` removed (ADR-091 Am. 2026-07-17), TLS helpers in `alknet-tls` (ADR-089 §5), `connect`/`ClientError` removed, `alknet-call` is a pure protocol crate with no TLS/transport deps.
|
||||||
---
|
---
|
||||||
|
|
||||||
# alknet-call
|
# alknet-call
|
||||||
|
|
||||||
Structured RPC: operations, request/response, streaming subscriptions, and service discovery. Implements `ProtocolHandler` on ALPN `alknet/call`. Runs over QUIC (quinn/iroh) and, via `Connection::from_stream` (ADR-065), over any `AsyncRead + AsyncWrite` transport.
|
Structured RPC: operations, request/response, streaming subscriptions, and service discovery. Implements `ProtocolHandler` on ALPN `alknet/call`. Runs over QUIC (quinn/iroh) and, via `Connection::from_stream` (ADR-065), over any `AsyncRead + AsyncWrite` transport. A pure protocol crate — no TLS or transport deps (the dial is in `alknet-client`, the TLS config is in `alknet-tls`).
|
||||||
|
|
||||||
## Documents
|
## Documents
|
||||||
|
|
||||||
@@ -46,6 +46,8 @@ Structured RPC: operations, request/response, streaming subscriptions, and servi
|
|||||||
| [030](../../decisions/030-peerentry-and-identity-id-decoupling.md) | PeerEntry and Identity.id Decoupling | `PeerId` source = `Identity.id` = `PeerEntry.peer_id` (stable); supersedes ADR-029's UUID source |
|
| [030](../../decisions/030-peerentry-and-identity-id-decoupling.md) | PeerEntry and Identity.id Decoupling | `PeerId` source = `Identity.id` = `PeerEntry.peer_id` (stable); supersedes ADR-029's UUID source |
|
||||||
| [032](../../decisions/032-forwarded-for-identity.md) | Forwarded-For Identity | `forwarded_for` on `OperationContext` and `call.requested`; metadata only, never used by `AccessControl::check` |
|
| [032](../../decisions/032-forwarded-for-identity.md) | Forwarded-For Identity | `forwarded_for` on `OperationContext` and `call.requested`; metadata only, never used by `AccessControl::check` |
|
||||||
| [033](../../decisions/033-storage-boundary-and-repo-adapter-pattern.md) | Storage Boundary and Repo/Adapter Pattern | Core defines repo traits + in-memory defaults; persistence adapters are separate crates |
|
| [033](../../decisions/033-storage-boundary-and-repo-adapter-pattern.md) | Storage Boundary and Repo/Adapter Pattern | Core defines repo traits + in-memory defaults; persistence adapters are separate crates |
|
||||||
|
| [089](../../decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — Native Client Dial Seam | `CallClient::connect` removed; the dial is in `alknet-client`; `ClientError` removed; `alknet-call` sheds TLS/transport deps (pure protocol crate) |
|
||||||
|
| [091](../../decisions/091-connectioncredentials-decouple-dial-from-call.md) | `ConnectionCredentials` — Decouple Dial from Call Protocol | `ConnectionCredentials`/`RemoteIdentity` in `alknet-core` (not `alknet-call`); `CallCredentials` removed (Am. 2026-07-17); `auth_token` is a per-request payload field |
|
||||||
|
|
||||||
## Relevant Open Questions
|
## Relevant Open Questions
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-09
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# alknet-call — Client and Adapters
|
# alknet-call — Client and Adapters
|
||||||
@@ -251,12 +251,12 @@ attribution, filtered by the calling peer's authorization). See
|
|||||||
The credential dimensions are split across two layers (ADR-091, amended
|
The credential dimensions are split across two layers (ADR-091, amended
|
||||||
2026-07-17):
|
2026-07-17):
|
||||||
|
|
||||||
- **`ConnectionCredentials`** (in `alknet-core`, moved from
|
- **`ConnectionCredentials`** (in `alknet-core`, per ADR-091) — the
|
||||||
`alknet-call` per ADR-091) — the **transport-level** credential
|
**transport-level** credential bundle, consumed by the dial
|
||||||
bundle, consumed by the dial (`AlknetClient`). Carries the two
|
(`AlknetClient`). Carries the two transport-identity dimensions:
|
||||||
transport-identity dimensions: `local_identity` (the local node's
|
`tls_identity` (the local node's `TlsIdentity`) and `remote_identity`
|
||||||
`TlsIdentity`) and `remote_identity` (the expected fingerprint). The
|
(the expected fingerprint). The dial does not depend on the call
|
||||||
dial does not depend on the call protocol for this type.
|
protocol for this type.
|
||||||
- **`auth_token`** — a **per-request payload field**, not a
|
- **`auth_token`** — a **per-request payload field**, not a
|
||||||
call-protocol credential bundle. `Dispatcher::resolve_identity`
|
call-protocol credential bundle. `Dispatcher::resolve_identity`
|
||||||
reads `payload.get("auth_token")` on each `call.requested` payload.
|
reads `payload.get("auth_token")` on each `call.requested` payload.
|
||||||
@@ -276,7 +276,7 @@ variables. The transport-identity dimensions (ADR-017 §7):
|
|||||||
```rust
|
```rust
|
||||||
// Transport-level (alknet-core, consumed by the dial — ADR-091)
|
// Transport-level (alknet-core, consumed by the dial — ADR-091)
|
||||||
pub struct ConnectionCredentials {
|
pub struct ConnectionCredentials {
|
||||||
pub local_identity: Option<TlsIdentity>, // RFC 7250 raw key or X.509
|
pub tls_identity: Option<TlsIdentity>, // RFC 7250 raw key or X.509
|
||||||
pub remote_identity: Option<RemoteIdentity>, // expected fingerprint (None = CA path / fail-closed)
|
pub remote_identity: Option<RemoteIdentity>, // expected fingerprint (None = CA path / fail-closed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,31 +286,29 @@ pub struct ConnectionCredentials {
|
|||||||
// Dispatcher::resolve_identity reads payload.get("auth_token").
|
// Dispatcher::resolve_identity reads payload.get("auth_token").
|
||||||
```
|
```
|
||||||
|
|
||||||
There is no call-protocol credential bundle. `CallCredentials` is
|
`RemoteIdentity` (ADR-017 §7, extended by ADR-034 §2) carries a
|
||||||
removed. The transport dimensions (`local_identity`, `remote_identity`)
|
fingerprint string the assembly layer derives from `Capabilities` when
|
||||||
moved to `ConnectionCredentials` in `alknet-core` per ADR-091.
|
the local node has a `PeerEntry` for the remote (the known-peer case →
|
||||||
|
fingerprint pin). `remote_identity: None` is the **public X.509
|
||||||
|
endpoint** case: the local node has no `PeerEntry` for the remote, so
|
||||||
|
there is no fingerprint to pin. Combined with an X.509 transport, `None`
|
||||||
|
selects CA verification (`WebPkiServerVerifier`) per the
|
||||||
|
verifier-selection rule in ADR-034 §3. Combined with an Ed25519
|
||||||
|
raw-key transport, `None` fails closed (raw-key remotes are always
|
||||||
|
known peers — no CA to fall back to). The `Option` is load-bearing, not
|
||||||
|
cosmetic: `Some(fingerprint)` means "pin this" (known peer), `None`
|
||||||
|
means "trust the CA or fail" (unknown remote). An implementer must not
|
||||||
|
default `remote_identity` to a placeholder value to "satisfy" the field
|
||||||
|
— `None` is a real state that drives verifier selection.
|
||||||
|
|
||||||
/// Expected identity of the remote node (ADR-017 §7, extended by
|
```rust
|
||||||
/// ADR-034 §2). Carries a fingerprint string the assembly layer
|
|
||||||
/// derives from `Capabilities` when the local node has a `PeerEntry`
|
|
||||||
/// for the remote (the known-peer case → fingerprint pin).
|
|
||||||
///
|
|
||||||
/// `remote_identity: None` is the **public X.509 endpoint** case: the
|
|
||||||
/// local node has no `PeerEntry` for the remote, so there is no
|
|
||||||
/// fingerprint to pin. Combined with an X.509 transport, `None`
|
|
||||||
/// selects CA verification (`WebPkiServerVerifier`) per the
|
|
||||||
/// verifier-selection rule in ADR-034 §3. Combined with an Ed25519
|
|
||||||
/// raw-key transport, `None` fails closed (raw-key remotes are always
|
|
||||||
/// known peers — no CA to fall back to).
|
|
||||||
///
|
|
||||||
/// The `Option` is therefore load-bearing, not cosmetic: `Some(fingerprint)`
|
|
||||||
/// means "pin this" (known peer), `None` means "trust the CA or fail"
|
|
||||||
/// (unknown remote). An implementer must not default `remote_identity`
|
|
||||||
/// to a placeholder value to "satisfy" the field — `None` is a real
|
|
||||||
/// state that drives verifier selection.
|
|
||||||
pub struct RemoteIdentity { pub fingerprint: String }
|
pub struct RemoteIdentity { pub fingerprint: String }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There is no call-protocol credential bundle. `CallCredentials` is
|
||||||
|
removed. The transport dimensions (`tls_identity`, `remote_identity`)
|
||||||
|
are in `ConnectionCredentials` in `alknet-core` per ADR-091.
|
||||||
|
|
||||||
- **TLS identity** — the local node's Ed25519 raw key (RFC 7250) or X.509 cert,
|
- **TLS identity** — the local node's Ed25519 raw key (RFC 7250) or X.509 cert,
|
||||||
derived from the vault at startup (ADR-020, ADR-026, ADR-027).
|
derived from the vault at startup (ADR-020, ADR-026, ADR-027).
|
||||||
- **Auth token** — an opaque call-protocol-level token, decrypted from the
|
- **Auth token** — an opaque call-protocol-level token, decrypted from the
|
||||||
@@ -783,7 +781,7 @@ Based on the gap analysis and the downstream unblock chain:
|
|||||||
| Abort cascade for nested calls | [ADR-016](../../decisions/016-abort-cascade-for-nested-calls.md) | Cross-node abort through `from_call` forwarding handler's `parent_request_id` |
|
| Abort cascade for nested calls | [ADR-016](../../decisions/016-abort-cascade-for-nested-calls.md) | Cross-node abort through `from_call` forwarding handler's `parent_request_id` |
|
||||||
| Operation error schemas | [ADR-023](../../decisions/023-operation-error-schemas.md) | `error_schemas` mirrored by `from_call` from remote op's spec |
|
| Operation error schemas | [ADR-023](../../decisions/023-operation-error-schemas.md) | `error_schemas` mirrored by `from_call` from remote op's spec |
|
||||||
| Streaming handler for subscriptions | [ADR-049](../../decisions/049-streaming-handler-for-subscriptions.md) | `from_call` `Subscription` ops register a `StreamingHandler` (`HandlerKind::Stream`) that calls `CallConnection::subscribe()` and forwards the remote stream; `Query`/`Mutation` stay `HandlerKind::Once` |
|
| Streaming handler for subscriptions | [ADR-049](../../decisions/049-streaming-handler-for-subscriptions.md) | `from_call` `Subscription` ops register a `StreamingHandler` (`HandlerKind::Stream`) that calls `CallConnection::subscribe()` and forwards the remote stream; `Query`/`Mutation` stay `HandlerKind::Once` |
|
||||||
| TLS identity redesign | [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | RFC 7250 raw key / X.509 cert dimensions of `CallCredentials` |
|
| TLS identity redesign | [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | RFC 7250 raw key / X.509 cert dimensions of the local `TlsIdentity` (now carried by `ConnectionCredentials.tls_identity`) |
|
||||||
| Outgoing-only X.509 and three peer roles | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Public X.509 endpoint is not a `PeerEntry` on the client side (no `PeerId`, not in peer graph); client-side verifier by `PeerEntry` presence (CA vs fingerprint pin); hub = mixed-fingerprint `PeerEntry` |
|
| Outgoing-only X.509 and three peer roles | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Public X.509 endpoint is not a `PeerEntry` on the client side (no `PeerId`, not in peer graph); client-side verifier by `PeerEntry` presence (CA vs fingerprint pin); hub = mixed-fingerprint `PeerEntry` |
|
||||||
| HD derivation for encryption keys | [ADR-020](../../decisions/020-hd-derivation-for-encryption-keys.md) | Vault-derived TLS identity material |
|
| HD derivation for encryption keys | [ADR-020](../../decisions/020-hd-derivation-for-encryption-keys.md) | Vault-derived TLS identity material |
|
||||||
| Vault key model | [ADR-026](../../decisions/026-vault-key-model-hd-derivation.md) | Vault-derived TLS identity material |
|
| Vault key model | [ADR-026](../../decisions/026-vault-key-model-hd-derivation.md) | Vault-derived TLS identity material |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-16
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# alknet-client
|
# alknet-client
|
||||||
@@ -186,12 +186,13 @@ impl AlknetClient {
|
|||||||
/// Iroh dial. Dials on `alpn` via the iroh endpoint. The iroh path
|
/// Iroh dial. Dials on `alpn` via the iroh endpoint. The iroh path
|
||||||
/// does NOT use `TlsClientConfig` — iroh has its own TLS (shares the
|
/// does NOT use `TlsClientConfig` — iroh has its own TLS (shares the
|
||||||
/// `Ed25519SecretKey`, not the rustls config — ADR-087 §3, ADR-089
|
/// `Ed25519SecretKey`, not the rustls config — ADR-087 §3, ADR-089
|
||||||
/// §3). The local key is extracted from `creds.local_identity`; the
|
/// §3). The local key is on the pre-built iroh endpoint (set when
|
||||||
/// remote `NodeId` is derived from `creds.remote_identity.fingerprint`
|
/// `with_iroh` configured it); the remote `NodeId` is derived from
|
||||||
/// (`ed25519:<hex>` → `NodeId::from_bytes`). The verifier is iroh's
|
/// `creds.remote_identity.fingerprint` (`ed25519:<hex>` →
|
||||||
/// `NodeId` match (fingerprint pin by another name — ADR-034 §3).
|
/// `NodeId::from_bytes`). The verifier is iroh's `NodeId` match
|
||||||
/// An unknown iroh remote fails closed (no CA). Feature-gated on
|
/// (fingerprint pin by another name — ADR-034 §3). An unknown iroh
|
||||||
/// `iroh`.
|
/// remote fails closed (no CA — `remote_identity` must be `Some`).
|
||||||
|
/// Feature-gated on `iroh`.
|
||||||
#[cfg(feature = "iroh")]
|
#[cfg(feature = "iroh")]
|
||||||
pub async fn dial_iroh(
|
pub async fn dial_iroh(
|
||||||
&self,
|
&self,
|
||||||
@@ -206,10 +207,10 @@ The two rustls dials (`dial_quic`, `dial_tcp_tls`) share
|
|||||||
pin for a known peer, CA-verify for an unknown X.509 remote, fail-closed
|
pin for a known peer, CA-verify for an unknown X.509 remote, fail-closed
|
||||||
for an unknown raw-key remote) and the ADR-084 crypto provider
|
for an unknown raw-key remote) and the ADR-084 crypto provider
|
||||||
(`aws_lc_rs`). The iroh dial is the exception: iroh has its own TLS and
|
(`aws_lc_rs`). The iroh dial is the exception: iroh has its own TLS and
|
||||||
takes the `Ed25519SecretKey` directly (extracted from
|
takes the `Ed25519SecretKey` directly (on the pre-built iroh endpoint,
|
||||||
`creds.local_identity`), not a `rustls::ClientConfig`. The consistency
|
not extracted from `creds` at dial time), not a `rustls::ClientConfig`.
|
||||||
is in the rule (ADR-034), not in the type — the same exception as the
|
The consistency is in the rule (ADR-034), not in the type — the same
|
||||||
server side (ADR-082, ADR-087 §3). All three dials take
|
exception as the server side (ADR-082, ADR-087 §3). All three dials take
|
||||||
`&ConnectionCredentials` — the unified transport-level credential
|
`&ConnectionCredentials` — the unified transport-level credential
|
||||||
bundle (ADR-091).
|
bundle (ADR-091).
|
||||||
|
|
||||||
@@ -422,22 +423,20 @@ two lines: `client.dial_quic(...).await?` then
|
|||||||
### Iroh — shares the key, not the config (client side too)
|
### Iroh — shares the key, not the config (client side too)
|
||||||
|
|
||||||
The iroh client dial, like the iroh server side (ADR-082, ADR-087 §3),
|
The iroh client dial, like the iroh server side (ADR-082, ADR-087 §3),
|
||||||
does not consume a `rustls::ClientConfig`. It takes the
|
does not consume a `rustls::ClientConfig`. The `Ed25519SecretKey` is set
|
||||||
`Ed25519SecretKey` directly and feeds it to
|
on the pre-built iroh endpoint at `with_iroh` time (the assembly layer
|
||||||
`iroh::SecretKey::from_bytes`. Iroh handles TLS internally. The
|
reads it from `StaticConfig` and feeds it to
|
||||||
verifier is iroh's `NodeId` match — the remote's `NodeId` (Ed25519
|
`iroh::Endpoint::builder().secret_key()`). The `dial_iroh` method
|
||||||
|
consumes only `creds.remote_identity` (deriving the remote `NodeId`);
|
||||||
|
the local key is not in `ConnectionCredentials` for the iroh path — it
|
||||||
|
is on the endpoint. The dial signature is unified — all three dials
|
||||||
|
take `&ConnectionCredentials` (ADR-091) — and the iroh dial simply
|
||||||
|
ignores the `tls_identity` field (the key is already on the endpoint).
|
||||||
|
The verifier is iroh's `NodeId` match — the remote's `NodeId` (Ed25519
|
||||||
public key) is verified against the expected `NodeId`, which is
|
public key) is verified against the expected `NodeId`, which is
|
||||||
fingerprint-pinning by another name. An unknown iroh remote fails
|
fingerprint-pinning by another name. An unknown iroh remote fails
|
||||||
closed (no CA to fall back to — ADR-034 §3, Assumption 1).
|
closed (no CA to fall back to — ADR-034 §3, Assumption 1).
|
||||||
|
|
||||||
The `dial_iroh` method extracts the key from
|
|
||||||
`creds.local_identity` (`ConnectionCredentials`) rather than taking a
|
|
||||||
separate `Ed25519SecretKey` parameter because the dial signature is
|
|
||||||
unified — all three dials take `&ConnectionCredentials` (ADR-091). The
|
|
||||||
assembly layer reads the key from `StaticConfig` (in core) and passes
|
|
||||||
it via `ConnectionCredentials`, same as the server side's iroh endpoint
|
|
||||||
construction.
|
|
||||||
|
|
||||||
### Non-Rust native clients (out of scope)
|
### Non-Rust native clients (out of scope)
|
||||||
|
|
||||||
The wire protocols (channels 9-byte chunk format — ADR-071; call
|
The wire protocols (channels 9-byte chunk format — ADR-071; call
|
||||||
@@ -641,7 +640,7 @@ let client = AlknetClient::new()
|
|||||||
|
|
||||||
// 3. Derive credentials from the vault (ADR-014 — no env vars).
|
// 3. Derive credentials from the vault (ADR-014 — no env vars).
|
||||||
let creds = ConnectionCredentials::new()
|
let creds = ConnectionCredentials::new()
|
||||||
.with_local_identity(TlsIdentity::RawKey(local_key))
|
.with_tls_identity(TlsIdentity::RawKey(local_key))
|
||||||
.with_remote_identity(RemoteIdentity {
|
.with_remote_identity(RemoteIdentity {
|
||||||
fingerprint: hub_fingerprint, // known peer → fingerprint pin
|
fingerprint: hub_fingerprint, // known peer → fingerprint pin
|
||||||
});
|
});
|
||||||
@@ -675,7 +674,7 @@ All design decisions are documented as ADRs in
|
|||||||
|-----|----------|---------|
|
|-----|----------|---------|
|
||||||
| [089](../../decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — native client dial seam | New crate `alknet-client`; client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key); resolves OQ-55; `alknet/register` named, wire protocol deferred (§3/§5 amended by ADR-091 — dial takes `ConnectionCredentials`, not `CallCredentials`) |
|
| [089](../../decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — native client dial seam | New crate `alknet-client`; client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key); resolves OQ-55; `alknet/register` named, wire protocol deferred (§3/§5 amended by ADR-091 — dial takes `ConnectionCredentials`, not `CallCredentials`) |
|
||||||
| [090](../../decisions/090-client-dial-socks5-proxy-seam.md) | Client-Dial SOCKS5 Proxy Seam | `AlknetClient` gains `with_socks5_proxy`; `dial_quic` routes via UDP ASSOCIATE, `dial_tcp_tls` via CONNECT, `dial_iroh` forces relay-only via an HTTP-to-SOCKS5 bridge; OQ-67 resolved; grounded in the quinn-proxy + iroh-proxy PoCs |
|
| [090](../../decisions/090-client-dial-socks5-proxy-seam.md) | Client-Dial SOCKS5 Proxy Seam | `AlknetClient` gains `with_socks5_proxy`; `dial_quic` routes via UDP ASSOCIATE, `dial_tcp_tls` via CONNECT, `dial_iroh` forces relay-only via an HTTP-to-SOCKS5 bridge; OQ-67 resolved; grounded in the quinn-proxy + iroh-proxy PoCs |
|
||||||
| [091](../../decisions/091-connectioncredentials-decouple-dial-from-call.md) | `ConnectionCredentials` — decouple dial from call protocol | The dial credential bundle is `ConnectionCredentials` (transport-level: `local_identity` + `remote_identity`), not `CallCredentials` (call-protocol-level); all three dial signatures unify on `&ConnectionCredentials`; `dial_iroh`'s `node_id` derived from `remote_identity`; `auth_token` is a per-request payload field; `CallCredentials` removed per Am. 2026-07-17 |
|
| [091](../../decisions/091-connectioncredentials-decouple-dial-from-call.md) | `ConnectionCredentials` — decouple dial from call protocol | The dial credential bundle is `ConnectionCredentials` (transport-level: `tls_identity` + `remote_identity`), not `CallCredentials` (call-protocol-level); all three dial signatures unify on `&ConnectionCredentials`; `dial_iroh`'s `node_id` derived from `remote_identity`; `auth_token` is a per-request payload field; `CallCredentials` removed per Am. 2026-07-17 |
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-15
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# alknet-core
|
# alknet-core
|
||||||
@@ -8,19 +8,21 @@ last_updated: 2026-07-15
|
|||||||
Shared types, auth, config, and identity for ALPN-based protocol
|
Shared types, auth, config, and identity for ALPN-based protocol
|
||||||
dispatch. Every handler crate depends on `alknet-core` for
|
dispatch. Every handler crate depends on `alknet-core` for
|
||||||
`ProtocolHandler`, `Connection`, `AuthContext`, `IdentityProvider`, and
|
`ProtocolHandler`, `Connection`, `AuthContext`, `IdentityProvider`, and
|
||||||
config types. The endpoint (`AlknetEndpoint`, `HandlerRegistry`) has
|
config types. The endpoint (`AlknetEndpoint`, `HandlerRegistry`) lives
|
||||||
been extracted to [`alknet-endpoint`](../endpoint/README.md) (ADR-083
|
in [`alknet-endpoint`](../endpoint/README.md) (ADR-083 Amendment
|
||||||
Amendment 2026-07-15; `EndpointError` is removed — both variants were
|
2026-07-15; `EndpointError` is removed — both variants were vestigial);
|
||||||
vestigial); core no longer carries the accept-loop runner or its
|
core does not carry the accept-loop runner or its transport deps
|
||||||
transport deps (quinn, iroh, rcgen, rustls-acme).
|
(quinn, iroh, rcgen, rustls-acme). `Connection::from_quinn` /
|
||||||
`Connection::from_quinn` / `from_iroh` stay in core's `types.rs` as
|
`from_iroh` are in core's `types.rs` as shared constructors (gated on
|
||||||
shared constructors (gated on core's `quinn` / `iroh` features).
|
core's `quinn` / `iroh` features).
|
||||||
`ConnectionCredentials` and `RemoteIdentity` move to `alknet-core`
|
|
||||||
(from `alknet-call`, per ADR-091) — the transport-level credential
|
`ConnectionCredentials` and `RemoteIdentity` live in `alknet-core` (per
|
||||||
bundle consumed by the dial (`alknet-client`) and by server-side
|
ADR-091) — the transport-level credential bundle consumed by the dial
|
||||||
transport construction. `ConnectionCredentials` (the transport-level credential
|
(`alknet-client`) and by server-side transport construction. There is no
|
||||||
bundle, including `auth_token`) stays in `alknet-call` — the dial does
|
call-protocol credential bundle: `CallCredentials` is removed (ADR-091
|
||||||
not carry call-protocol dimensions.
|
Am. 2026-07-17 — its `auth_token` field had no reader; `auth_token` is
|
||||||
|
a per-request payload field on `call.requested`, not a transport
|
||||||
|
credential).
|
||||||
|
|
||||||
## Documents
|
## Documents
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,26 @@
|
|||||||
---
|
---
|
||||||
status: deprecated
|
status: deprecated
|
||||||
last_updated: 2026-07-15
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Endpoint (moved to `alknet-endpoint`)
|
# Endpoint (in `alknet-endpoint`)
|
||||||
|
|
||||||
> **This document is deprecated.** The `AlknetEndpoint` and
|
> **This document is deprecated.** The `AlknetEndpoint` and
|
||||||
> `HandlerRegistry` types have been extracted from `alknet-core` into a
|
> `HandlerRegistry` types live in a separate crate, `alknet-endpoint`
|
||||||
> new crate `alknet-endpoint` (ADR-083 Amendment 2026-07-15).
|
> (ADR-083 Amendment 2026-07-15). `EndpointError` is removed (both
|
||||||
> `EndpointError` is removed (both variants were vestigial). The
|
> variants were vestigial). The canonical spec is
|
||||||
> canonical spec is now
|
|
||||||
> [`crates/endpoint/README.md`](../endpoint/README.md).
|
> [`crates/endpoint/README.md`](../endpoint/README.md).
|
||||||
>
|
>
|
||||||
> The shared types the endpoint imports (`ProtocolHandler`,
|
> The shared types the endpoint imports (`ProtocolHandler`,
|
||||||
> `Connection`, `AuthContext`, `IdentityProvider`, `DynamicConfig`) stay
|
> `Connection`, `AuthContext`, `IdentityProvider`, `DynamicConfig`) are
|
||||||
> in `alknet-core` — see [`core-types.md`](core-types.md),
|
> in `alknet-core` — see [`core-types.md`](core-types.md),
|
||||||
> [`auth.md`](auth.md), [`config.md`](config.md).
|
> [`auth.md`](auth.md), [`config.md`](config.md).
|
||||||
|
|
||||||
## Historical summary
|
## What is in `alknet-core`
|
||||||
|
|
||||||
The endpoint was originally in `alknet-core/endpoint.rs` as the central
|
`Connection::from_quinn` / `from_iroh` are in core's `types.rs` — they
|
||||||
runtime type — a multi-transport accept-loop runner that dispatches
|
|
||||||
incoming connections by ALPN (ADR-010, ADR-083). ADR-082 extracted the
|
|
||||||
TLS setup code to `alknet-tls`; ADR-083 restructured the endpoint to
|
|
||||||
take pre-built transports via `with_quinn` / `with_iroh` /
|
|
||||||
`with_tcp_tls` (no TLS config); ADR-083 Amendment 2026-07-15 extracted
|
|
||||||
the endpoint itself into `alknet-endpoint` so that handler crates no
|
|
||||||
longer transitively link quinn/iroh/rcgen via core.
|
|
||||||
|
|
||||||
The endpoint's semantics — ALPN dispatch, `HandlerRegistry`, accept
|
|
||||||
loops, public `dispatch` for SSH/WT, graceful shutdown — are unchanged
|
|
||||||
by the extraction. See
|
|
||||||
[`crates/endpoint/README.md`](../endpoint/README.md) for the current
|
|
||||||
spec and [ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md)
|
|
||||||
for the full decision.
|
|
||||||
|
|
||||||
## What stayed in `alknet-core`
|
|
||||||
|
|
||||||
`Connection::from_quinn` / `from_iroh` stay in core's `types.rs` — they
|
|
||||||
are shared-type constructors used by both the endpoint's accept loop
|
are shared-type constructors used by both the endpoint's accept loop
|
||||||
(server) and `alknet-client`'s dial (client, ADR-089), gated on core's
|
(server, in `alknet-endpoint`) and `alknet-client`'s dial (client,
|
||||||
`quinn` / `iroh` features. See
|
ADR-089), gated on core's `quinn` / `iroh` features. See
|
||||||
[ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md) §"The
|
[ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md) §"The
|
||||||
`quinn` feature split".
|
`quinn` feature split".
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-15
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# alknet-endpoint
|
# alknet-endpoint
|
||||||
@@ -22,24 +22,23 @@ It does not build transports and does not build TLS configs — the
|
|||||||
assembly layer does both (transports from `alknet-tls`'s
|
assembly layer does both (transports from `alknet-tls`'s
|
||||||
`TlsServerConfig`, per ADR-082).
|
`TlsServerConfig`, per ADR-082).
|
||||||
|
|
||||||
`alknet-endpoint` is extracted from `alknet-core` (ADR-083 Amendment
|
`alknet-endpoint` is a leaf consumer of `alknet-core`'s shared types
|
||||||
2026-07-15). The extraction is structural pruning, not a refactor: the
|
(it imports `auth`, `config`, `types`; nothing in core imports from
|
||||||
endpoint is a leaf consumer of core's shared types (it imports `auth`,
|
it), depended on by the assembly layer — a different audience than the
|
||||||
`config`, `types`; nothing in core imports from it), depended on by a
|
shared types (every handler crate). No handler crate imports
|
||||||
different audience (the assembly layer) than the shared types (every
|
`AlknetEndpoint` or `HandlerRegistry` — they depend on `alknet-core`
|
||||||
handler crate). No handler crate imports `AlknetEndpoint` or
|
for `ProtocolHandler`, `Connection`, `AuthContext`, and types only.
|
||||||
`HandlerRegistry` — they depend on `alknet-core` for
|
This keeps the heavy transport deps (quinn, iroh, tokio-rustls) out of
|
||||||
`ProtocolHandler`, `Connection`, `AuthContext`, and types only.
|
the handler crates' dep closure. (`EndpointError` is removed — see
|
||||||
(`EndpointError` is removed — see below.)
|
below.)
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
`alknet-core` was two things welded: shared types (depended on by every
|
Separating the endpoint from the shared-types crate lets `alknet-core`
|
||||||
handler crate) + the endpoint (depended on by zero handler crates).
|
be the lightweight types+auth+config crate that every handler crate
|
||||||
Extracting the endpoint into `alknet-endpoint` lets core shed the heavy
|
wants, while the accept-loop runner (which only the assembly layer
|
||||||
transport deps (quinn, iroh, rcgen, rustls-acme) and become the
|
depends on) carries the heavy transport deps. See
|
||||||
lightweight types+auth+config crate the handler crates actually want.
|
[ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md)
|
||||||
See [ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md)
|
|
||||||
§"Amendment 2026-07-15 — crate extraction" for the full rationale,
|
§"Amendment 2026-07-15 — crate extraction" for the full rationale,
|
||||||
including the dependency data and the symmetry with `alknet-client`.
|
including the dependency data and the symmetry with `alknet-client`.
|
||||||
|
|
||||||
@@ -332,15 +331,16 @@ The endpoint takes the pre-built transports; the assembly layer built
|
|||||||
them from `alknet-tls`'s `TlsServerConfig`s. The endpoint does not see
|
them from `alknet-tls`'s `TlsServerConfig`s. The endpoint does not see
|
||||||
`alknet-tls` — it sees `quinn::Endpoint` and `TlsAcceptor`.
|
`alknet-tls` — it sees `quinn::Endpoint` and `TlsAcceptor`.
|
||||||
|
|
||||||
## What `alknet-core` looks like after the extraction
|
## What `alknet-core` looks like
|
||||||
|
|
||||||
Core loses the endpoint module (~1600 LOC) and 5 heavy deps (`quinn`,
|
Core is the lightweight types+auth+config+ownership+store+fingerprint
|
||||||
`iroh`, `rcgen`, `rustls-pemfile`, `rustls-acme`). The remaining surface
|
crate (~3200 LOC, no `quinn`/`iroh`/`rcgen`/`rustls-pemfile`/
|
||||||
is the lightweight types+auth+config+ownership+store+fingerprint crate.
|
`rustls-acme` deps). The endpoint module is not in core; the accept
|
||||||
See [ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md)
|
loops are here. See [ADR-083](../../decisions/083-endpoint-as-accept-
|
||||||
§"Amendment 2026-07-15 — crate extraction" §"What `alknet-core` looks
|
loop-runner.md) §"Amendment 2026-07-15 — crate extraction" §"What
|
||||||
like after" for the module-level table and the `quinn` feature split
|
`alknet-core` looks like after" for the module-level table and the
|
||||||
(`Connection::from_quinn` stays in core; the accept loop moves here).
|
`quinn` feature split (`Connection::from_quinn` stays in core; the
|
||||||
|
accept loop is here).
|
||||||
|
|
||||||
## Design Decisions
|
## Design Decisions
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: reviewed
|
status: reviewed
|
||||||
last_updated: 2026-07-15
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# alknet-tls
|
# alknet-tls
|
||||||
@@ -17,16 +17,9 @@ one verifier rule, N clients.
|
|||||||
|
|
||||||
## What
|
## What
|
||||||
|
|
||||||
`alknet-tls` extracts the TLS setup that was welded to the quinn endpoint
|
`alknet-tls` provides `TlsServerConfig` and `TlsClientConfig` —
|
||||||
in `alknet-core`. The existing code (`endpoint.rs`) builds a
|
shareable TLS setup types that a deployment builds once and hands to
|
||||||
`rustls::ServerConfig` from a `TlsIdentity`, then **consumes** it into a
|
whichever transports it runs:
|
||||||
`quinn::ServerConfig` — making it impossible to reuse the same cert for a
|
|
||||||
TCP+TLS listener. ACME is worse: the `AcmeState` task is spawned inside
|
|
||||||
the quinn endpoint, so a TCP+TLS listener would need its own ACME state
|
|
||||||
machine (two orders for the same domain, two cert caches, potential
|
|
||||||
Let's Encrypt rate-limiting).
|
|
||||||
|
|
||||||
`alknet-tls` fixes this by making the TLS config **shareable**:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct TlsServerConfig {
|
pub struct TlsServerConfig {
|
||||||
@@ -64,15 +57,17 @@ transports.
|
|||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
`alknet-core` builds the `rustls::ServerConfig` once, then consumes it
|
Without a shareable TLS config, a `rustls::ServerConfig` built for one
|
||||||
into a `quinn::ServerConfig` — making the cert unreusable for a TCP+TLS
|
transport gets consumed into that transport's wrapper (e.g.
|
||||||
listener. For ACME the problem is worse: the `AcmeState` task is spawned
|
`quinn::ServerConfig`), making the cert unreusable for a TCP+TLS
|
||||||
inside the quinn endpoint, so a TCP+TLS listener would need a second ACME
|
listener. For ACME the problem is worse: the `AcmeState` task spawned
|
||||||
state machine for the same domain (duplicate orders, divergent cert
|
inside the quinn endpoint means a TCP+TLS listener would need a second
|
||||||
caches, Let's Encrypt rate-limit risk). The full rationale, including
|
ACME state machine for the same domain (duplicate orders, divergent cert
|
||||||
the cert-reuse problem, the ACME worst case, and the three reasons a
|
caches, Let's Encrypt rate-limit risk). `alknet-tls` isolates TLS setup
|
||||||
separate crate is the right shape (dependency isolation, ACME weight,
|
from the transport so one config serves all transports. The full
|
||||||
quinn/iroh having their own TLS), is in
|
rationale, including the cert-reuse problem, the ACME worst case, and
|
||||||
|
the three reasons a separate crate is the right shape (dependency
|
||||||
|
isolation, ACME weight, quinn/iroh having their own TLS), is in
|
||||||
[ADR-082](../../decisions/082-alknet-tls-extraction.md).
|
[ADR-082](../../decisions/082-alknet-tls-extraction.md).
|
||||||
|
|
||||||
### The three endpoint types (ADR-086)
|
### The three endpoint types (ADR-086)
|
||||||
@@ -107,64 +102,73 @@ transports the deployment runs.
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### What moves from `alknet-core` to `alknet-tls` (server side)
|
### Server-side contents
|
||||||
|
|
||||||
| Component | Current location | New location |
|
The server-side TLS setup — `rustls::ServerConfig` construction, cert
|
||||||
|-----------|-----------------|-------------|
|
resolvers, the ACME state machine — is in `alknet-tls/src/server.rs`.
|
||||||
| `TlsIdentity` enum | `alknet-core/config.rs` | **stays in core** (it's a config type) |
|
These components were originally part of `alknet-core`'s endpoint
|
||||||
| `Ed25519SecretKey` | `alknet-core/config.rs` | **stays in core** (config type) |
|
module (quinn-gated); ADR-082 moved them into `alknet-tls` so a
|
||||||
| `build_rustls_server_config()` | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` (unconditional) |
|
`TlsServerConfig` is shareable across transports rather than consumed
|
||||||
| `build_quinn_server_config_from_rustls()` | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` (`for_quinn()` — wraps rustls config in `QuicServerConfig`) |
|
into a single transport's wrapper.
|
||||||
| `TlsSetup` (ACME state machine) | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` (the `TlsServerConfig::new` ACME path) |
|
|
||||||
| `RawKeyCertResolver` | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` |
|
|
||||||
| `Ed25519SigningKey` | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` (consolidates with the `alknet-call` duplicate — see client table) |
|
|
||||||
| `AcceptAnyCertVerifier` | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` |
|
|
||||||
| `SelfSignedCert` / `generate_self_signed_cert()` | `alknet-core/endpoint.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` |
|
|
||||||
| `load_cert_chain()` / `load_private_key()` | `alknet-core/endpoint.rs` | `alknet-tls` (consolidates with the `alknet-call` duplicate — see client table) |
|
|
||||||
| `fingerprint.rs` | `alknet-core/fingerprint.rs` | **stays in core** (shared by server + client; the client-side `FingerprintPinVerifier` is now in `alknet-tls` per ADR-089 §5, so both consumers are co-located; production code uses `sha2` + manual DER only — `rustls` is test-only. See OQ-59 — the original dep-edge concern that motivated keeping `fingerprint.rs` in core is dissolved by ADR-089 §5.) |
|
|
||||||
|
|
||||||
### What moves from `alknet-call` to `alknet-tls` (client side)
|
| Component | Notes |
|
||||||
|
|-----------|-------|
|
||||||
|
| `TlsServerConfig` | The central type — wraps `rustls::ServerConfig` + the optional ACME task handle |
|
||||||
|
| `build_rustls_server_config()` | Unconditional; called by `TlsServerConfig::new` |
|
||||||
|
| `for_quinn()` | Wraps the rustls config in a `QuicServerConfig` (feature-gated on `quinn`) |
|
||||||
|
| `TlsSetup` / ACME path | The `TlsServerConfig::new` ACME branch spawns the state-machine task |
|
||||||
|
| `RawKeyCertResolver` | Presents an Ed25519 key as an RFC 7250 raw public key server cert |
|
||||||
|
| `Ed25519SigningKey` | One copy in `alknet-tls`, shared by server + client (see below) |
|
||||||
|
| `AcceptAnyCertVerifier` | Accepts any client cert and extracts the fingerprint (raw-key servers don't pin client certs) |
|
||||||
|
| `SelfSignedCert` / `generate_self_signed_cert()` | The dev `SelfSigned` identity path |
|
||||||
|
| `load_cert_chain()` / `load_private_key()` | In `pem.rs`; one copy, shared by server + client |
|
||||||
|
|
||||||
`TlsClientConfig::new` (ADR-087) centralizes the client-side verifier
|
The config types `TlsIdentity` and `Ed25519SecretKey` live in
|
||||||
selection + provider wiring + client-auth cert presentation that
|
`alknet-core` (`config.rs`) — `StaticConfig` holds a `TlsIdentity`, and
|
||||||
currently lives in `alknet-call/src/client/call_client.rs`. The
|
config types belong in core. `alknet-tls` imports them. `fingerprint.rs`
|
||||||
extraction is the client-side analogue of the server-side
|
lives in core because it is shared by both the server path (the
|
||||||
`endpoint.rs` extraction above.
|
endpoint extracts the fingerprint from the client cert) and the client
|
||||||
|
path (`FingerprintPinVerifier`, in `alknet-tls`, matches the server's
|
||||||
| Component | Current location | New location |
|
cert against a pinned fingerprint). The production code in
|
||||||
|-----------|-----------------|-------------|
|
`fingerprint.rs` uses only `sha2` and manual DER parsing; the
|
||||||
| `build_quinn_client_config()` | `alknet-call/client/call_client.rs` (`#[cfg(feature = "quinn")]`) | `alknet-tls` (`TlsClientConfig::new` + `for_quinn()`) |
|
`rustls::sign` usage is in the test helper only. See OQ-59 — the
|
||||||
| `build_client_auth()` | `alknet-call/client/call_client.rs` | `alknet-tls` (client-auth cert resolver construction inside `TlsClientConfig::new`) |
|
original dep-edge concern that motivated keeping `fingerprint.rs` in
|
||||||
| `select_server_verifier()` | `alknet-call/client/call_client.rs` | `alknet-tls` (ADR-034 verifier selection inside `TlsClientConfig::new`) |
|
core is dissolved by ADR-089 §5 (`FingerprintPinVerifier` is in
|
||||||
| `load_platform_root_cert_store()` | `alknet-call/client/call_client.rs` | `alknet-tls` (the unknown-X.509-remote CA path inside `TlsClientConfig::new`) |
|
|
||||||
| `FingerprintPinVerifier` | `alknet-call/client/call_client.rs` | `alknet-tls` (moved — it is a TLS concern; `TlsClientConfig::new` constructs it; moving it lets `alknet-call` shed its direct `rustls` dep entirely per ADR-089 §5) |
|
|
||||||
| `Ed25519SigningKey` (client-side copy) | `alknet-call/client/call_client.rs` | `alknet-tls` (consolidates with the `endpoint.rs` duplicate — one copy in `alknet-tls`) |
|
|
||||||
| `RawKeyClientCertResolver` | `alknet-call/client/call_client.rs` | `alknet-tls` |
|
|
||||||
| `NoClientCertResolver` | `alknet-call/client/call_client.rs` | `alknet-tls` |
|
|
||||||
| `load_cert_chain()` / `load_private_key()` (client-side copies) | `alknet-call/client/call_client.rs` | `alknet-tls` (consolidates with the `endpoint.rs` duplicate — one copy in `alknet-tls`) |
|
|
||||||
| `CallClient::connect` | `alknet-call/client/call_client.rs` | **removed** (ADR-089 §5 — the dial is extracted to `AlknetClient`; `CallClient` keeps only `spawn_dispatch`, shedding its TLS/transport deps) |
|
|
||||||
|
|
||||||
**Consolidation note.** `Ed25519SigningKey` and
|
|
||||||
`load_cert_chain`/`load_private_key` are currently **duplicated** across
|
|
||||||
`endpoint.rs` (server) and `call_client.rs` (client). After extraction
|
|
||||||
there is one copy of each in `alknet-tls`, used by both
|
|
||||||
`TlsServerConfig::new` and `TlsClientConfig::new`. Both call sites
|
|
||||||
(`endpoint.rs`'s server path, `call_client.rs`'s client path) are
|
|
||||||
updated to import from `alknet-tls`.
|
|
||||||
|
|
||||||
`TlsIdentity` and `Ed25519SecretKey` stay in core because they're config
|
|
||||||
types — `StaticConfig` holds a `TlsIdentity`, and config types belong in
|
|
||||||
core. `alknet-tls` re-exports them for convenience. `fingerprint.rs` stays
|
|
||||||
in core because it's shared by both the server path (endpoint extracts
|
|
||||||
fingerprint from the client cert) and the client path
|
|
||||||
(`FingerprintPinVerifier` — now in `alknet-tls` per ADR-089 §5 —
|
|
||||||
matches the server's cert against a pinned fingerprint).
|
|
||||||
The production code in `fingerprint.rs` uses only `sha2` and manual DER
|
|
||||||
parsing — the `rustls::sign` usage is in the test helper only. See OQ-59
|
|
||||||
(the original dep-edge concern that motivated keeping `fingerprint.rs`
|
|
||||||
in core is dissolved by ADR-089 §5 — `FingerprintPinVerifier` moved to
|
|
||||||
`alknet-tls`, so its consumers are co-located).
|
`alknet-tls`, so its consumers are co-located).
|
||||||
|
|
||||||
|
### Client-side contents
|
||||||
|
|
||||||
|
The client-side TLS setup — verifier selection, client-auth cert
|
||||||
|
presentation, provider wiring — is in `alknet-tls/src/client.rs`.
|
||||||
|
These components were originally part of `alknet-call`'s client
|
||||||
|
module (quinn-gated); ADR-087 / ADR-089 §5 moved them into `alknet-tls`
|
||||||
|
so `alknet-call` has no direct `rustls` dep and the verifier selection
|
||||||
|
is shared across all outbound dials.
|
||||||
|
|
||||||
|
| Component | Notes |
|
||||||
|
|-----------|-------|
|
||||||
|
| `TlsClientConfig::new` | Builds a `rustls::ClientConfig` from `ConnectionCredentials` + ALPN; runs ADR-034 verifier selection + ADR-084 provider wiring + client-auth cert presentation |
|
||||||
|
| `for_quinn()` | Wraps the rustls config in a `quinn::ClientConfig` (feature-gated on `quinn`) |
|
||||||
|
| `into_rustls_config()` | Returns the inner `rustls::ClientConfig` for consumers that build their own transport wrapper (e.g. `dial_tcp_tls` wraps it in a `TlsConnector`) |
|
||||||
|
| `build_client_auth()` | Constructs the client-auth cert resolver inside `TlsClientConfig::new` |
|
||||||
|
| `select_server_verifier()` | ADR-034 verifier selection (fingerprint pin / CA / fail-closed) inside `TlsClientConfig::new` |
|
||||||
|
| `load_platform_root_cert_store()` | The unknown-X.509-remote CA path inside `TlsClientConfig::new` |
|
||||||
|
| `FingerprintPinVerifier` | A TLS concern; `TlsClientConfig::new` constructs it. Locating it in `alknet-tls` lets `alknet-call` have no direct `rustls` dep (ADR-089 §5) |
|
||||||
|
| `RawKeyClientCertResolver` | Presents the local key as an RFC 7250 raw public key client cert |
|
||||||
|
| `NoClientCertResolver` | The no-client-cert path |
|
||||||
|
| `Ed25519SigningKey` | One copy in `alknet-tls` (`signing.rs`), shared by server + client |
|
||||||
|
| `load_cert_chain()` / `load_private_key()` | In `pem.rs`; one copy, shared by server + client |
|
||||||
|
|
||||||
|
`Ed25519SigningKey` and `load_cert_chain`/`load_private_key` are single
|
||||||
|
copies in `alknet-tls`, used by both `TlsServerConfig::new` and
|
||||||
|
`TlsClientConfig::new`. Before the extraction these were duplicated
|
||||||
|
across the server (in core's endpoint module) and the client (in call's
|
||||||
|
client module); the extraction consolidated them.
|
||||||
|
|
||||||
|
`CallClient::connect` is removed (ADR-089 §5) — the dial is in
|
||||||
|
`AlknetClient` (`alknet-client`); `CallClient` keeps only
|
||||||
|
`spawn_dispatch`, and `alknet-call` has no TLS/transport deps.
|
||||||
|
|
||||||
### `TlsServerConfig`
|
### `TlsServerConfig`
|
||||||
|
|
||||||
The central type. Built once from a `TlsIdentity` + ALPN list, shared
|
The central type. Built once from a `TlsIdentity` + ALPN list, shared
|
||||||
@@ -230,12 +234,12 @@ architecture decision.
|
|||||||
|
|
||||||
### Behavior-preservation invariants
|
### Behavior-preservation invariants
|
||||||
|
|
||||||
The extraction must preserve these load-bearing TLS behaviors. They
|
These load-bearing TLS behaviors must be preserved. They originate from
|
||||||
originate from [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md),
|
[ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md),
|
||||||
which established the `TlsIdentity` model, the `Acme` variant, and the
|
which established the `TlsIdentity` model, the `Acme` variant, and the
|
||||||
`acme-tls/1` ALPN challenge handling. An implementer who omits any of
|
`acme-tls/1` ALPN challenge handling. Omitting any of them produces a
|
||||||
these produces a crate that compiles and passes type-checks but silently
|
crate that compiles and passes type-checks but silently changes TLS
|
||||||
changes TLS behavior:
|
behavior:
|
||||||
|
|
||||||
- **`max_early_data_size = u32::MAX`** on all server config paths (X509,
|
- **`max_early_data_size = u32::MAX`** on all server config paths (X509,
|
||||||
RawKey, SelfSigned, ACME). Enables 0-RTT / early data. Omitting it
|
RawKey, SelfSigned, ACME). Enables 0-RTT / early data. Omitting it
|
||||||
@@ -278,8 +282,8 @@ impl TlsServerConfig {
|
|||||||
/// build their own transport-specific wrapper not covered by
|
/// build their own transport-specific wrapper not covered by
|
||||||
/// `for_quinn` / `for_tcp_tls`. No current consumer (iroh reads the
|
/// `for_quinn` / `for_tcp_tls`. No current consumer (iroh reads the
|
||||||
/// `Ed25519SecretKey` directly, not the rustls config — see "Iroh:
|
/// `Ed25519SecretKey` directly, not the rustls config — see "Iroh:
|
||||||
/// shares the key, not the rustls config" below); kept as a
|
/// shares the key, not the rustls config" below); retained for
|
||||||
/// forward-looking accessor for future transport wrappers.
|
/// transport wrappers that do not fit `for_quinn` / `for_tcp_tls`.
|
||||||
pub fn rustls_config(&self) -> &rustls::ServerConfig;
|
pub fn rustls_config(&self) -> &rustls::ServerConfig;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -291,7 +295,7 @@ the `Endpoint`, using RFC 7250 raw keys. It does not consume a
|
|||||||
`rustls::ServerConfig` — it takes an `iroh::SecretKey` and handles TLS
|
`rustls::ServerConfig` — it takes an `iroh::SecretKey` and handles TLS
|
||||||
internally. So `alknet-tls` does not have a `for_iroh()` method. Instead,
|
internally. So `alknet-tls` does not have a `for_iroh()` method. Instead,
|
||||||
the assembly layer reads the `Ed25519SecretKey` from `StaticConfig`
|
the assembly layer reads the `Ed25519SecretKey` from `StaticConfig`
|
||||||
(stays in core) and passes it to iroh's `Endpoint::builder().secret_key()`
|
(lives in core) and passes it to iroh's `Endpoint::builder().secret_key()`
|
||||||
directly. `alknet-tls` is involved only when iroh is not the sole
|
directly. `alknet-tls` is involved only when iroh is not the sole
|
||||||
transport — in that case, the same `Ed25519SecretKey` feeds both
|
transport — in that case, the same `Ed25519SecretKey` feeds both
|
||||||
`TlsServerConfig::new(TlsIdentity::RawKey(key), ...)` (for quinn/TCP) and
|
`TlsServerConfig::new(TlsIdentity::RawKey(key), ...)` (for quinn/TCP) and
|
||||||
@@ -346,22 +350,20 @@ alknet-tls
|
|||||||
`rustls-native-certs` and `webpki-roots` are always-present deps (not
|
`rustls-native-certs` and `webpki-roots` are always-present deps (not
|
||||||
feature-gated) because the unknown-X.509-remote CA-verification path in
|
feature-gated) because the unknown-X.509-remote CA-verification path in
|
||||||
`TlsClientConfig::new` is needed by any client dialing a public X.509
|
`TlsClientConfig::new` is needed by any client dialing a public X.509
|
||||||
endpoint, regardless of transport (QUIC or TCP+TLS). In the
|
endpoint, regardless of transport (QUIC or TCP+TLS). They are not gated
|
||||||
pre-extraction code these lived in `alknet-call` behind the `quinn`
|
under `quinn`/`tcp` — a TCP+TLS-only or QUIC-only deployment both need
|
||||||
feature; the extraction (ADR-089 §5) moves them to `alknet-tls` ungated,
|
the CA path. `alknet-call` does not depend on them (the dial's TLS
|
||||||
and `alknet-call` sheds the deps entirely.
|
deps are in `alknet-tls`/`alknet-client` now).
|
||||||
|
|
||||||
`alknet-core` loses `rustls-pemfile`, `rcgen`, and `rustls-acme` from
|
`alknet-core` does not depend on `rustls-pemfile`, `rcgen`, or
|
||||||
its dependencies — the cert-loading, self-signed generation, and ACME
|
`rustls-acme` — cert-loading, self-signed generation, and the ACME
|
||||||
machinery move to `alknet-tls`. Core's `acme` feature
|
state machine are in `alknet-tls` (on `TlsServerConfig`, not on
|
||||||
(`acme = ["dep:rustls-acme"]` in `Cargo.toml` and the
|
`AlknetEndpoint`). Core has no `acme` feature. Core does keep `quinn`
|
||||||
`#[cfg(feature = "acme")]` gates on `acme_state_handle` in `endpoint.rs`)
|
and `iroh` (for `Connection::from_quinn` / `from_iroh` — the shared
|
||||||
becomes vestigial after the extraction and is removed — the ACME state
|
constructors the endpoint and the dial both use),
|
||||||
machine now lives on `TlsServerConfig` in `alknet-tls`, not on
|
`ed25519-dalek` (`Ed25519SecretKey` in `config.rs`), and `rustls` /
|
||||||
`AlknetEndpoint`. Core keeps `quinn` and `iroh` (the endpoint struct and
|
`rustls-pki-types` (`fingerprint.rs` uses `rustls::pki_types` in
|
||||||
accept loops remain in core), `ed25519-dalek` (`Ed25519SecretKey` stays
|
production and `rustls::sign` in the test helper
|
||||||
in `config.rs`), and `rustls` / `rustls-pki-types` (`fingerprint.rs` uses
|
|
||||||
`rustls::pki_types` in production and `rustls::sign` in the test helper
|
|
||||||
`build_ed25519_spki_der` — see OQ-59).
|
`build_ed25519_spki_der` — see OQ-59).
|
||||||
|
|
||||||
> **Terminology — hub, worker, hub-worker.** A *hub* is a node that
|
> **Terminology — hub, worker, hub-worker.** A *hub* is a node that
|
||||||
@@ -374,47 +376,11 @@ in `config.rs`), and `rustls` / `rustls-pki-types` (`fingerprint.rs` uses
|
|||||||
> "assembly layer" (ADR-014) is the deployment binary that wires crates
|
> "assembly layer" (ADR-014) is the deployment binary that wires crates
|
||||||
> — in practice, today, usually a hub or hub-worker.
|
> — in practice, today, usually a hub or hub-worker.
|
||||||
|
|
||||||
### Implementation ordering
|
### What `AlknetEndpoint` (in `alknet-endpoint`) does
|
||||||
|
|
||||||
`alknet-tls` is greenfield — `crates/alknet-tls` does not exist yet. The
|
`AlknetEndpoint` takes **no TLS config at all** — it is a
|
||||||
endpoint section below ("What `AlknetEndpoint` does after the refactor")
|
multi-transport accept-loop runner. TCP+TLS is an owned transport (via
|
||||||
describes the **post-refactor target**, not the current source. The
|
`with_tcp_tls`), not an external loop:
|
||||||
current `crates/alknet-core/src/endpoint.rs` is the **extraction
|
|
||||||
source** — `AlknetEndpoint::new(static_config, ...)` builds TLS
|
|
||||||
internally, the shape ADR-083 replaces. The endpoint is extracted into
|
|
||||||
a new crate `alknet-endpoint` (ADR-083 Amendment 2026-07-15) as part of
|
|
||||||
this work. The extraction and refactor are **sequenced**, not
|
|
||||||
simultaneous:
|
|
||||||
|
|
||||||
1. **`alknet-tls` first** — build the crate in isolation. `TlsServerConfig`
|
|
||||||
and `TlsClientConfig` are unit-testable against `TlsIdentity` without
|
|
||||||
touching the endpoint. This is the greenfield step.
|
|
||||||
2. **`alknet-endpoint` second** — build the new endpoint crate fresh
|
|
||||||
against the ADR-083 shape (`new(handlers, dynamic,
|
|
||||||
identity_provider, drain_timeout)` + `with_quinn` / `with_iroh` /
|
|
||||||
`with_tcp_tls`), importing `Connection`/`ProtocolHandler`/`AuthContext`
|
|
||||||
from `alknet-core` and taking pre-built transports (no TLS config —
|
|
||||||
the assembly layer builds those via `alknet-tls`). The old
|
|
||||||
`crates/alknet-core/src/endpoint.rs` is deleted.
|
|
||||||
3. **Assembly layer last** — the deployment binary (hub/worker) builds
|
|
||||||
the `TlsServerConfig`(s) and `TlsClientConfig`(s), the transports, and
|
|
||||||
hands them to `AlknetEndpoint` (in `alknet-endpoint`) via the builder
|
|
||||||
methods.
|
|
||||||
|
|
||||||
A compilable intermediate state exists after step 1: `alknet-tls` built
|
|
||||||
and tested standalone, with `endpoint.rs` still in its old shape. The
|
|
||||||
call sites for `TlsServerConfig` / `TlsClientConfig` do not exist until
|
|
||||||
step 2/3 — an implementer testing step 1 writes tests against the TLS
|
|
||||||
types directly, not against a wired-up endpoint.
|
|
||||||
|
|
||||||
### What `AlknetEndpoint` (in `alknet-endpoint`) does after the refactor
|
|
||||||
|
|
||||||
`AlknetEndpoint::new()` currently builds `TlsSetup` internally. After
|
|
||||||
the refactor (see [ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md)),
|
|
||||||
the endpoint (extracted into `alknet-endpoint` per ADR-083 Amendment
|
|
||||||
2026-07-15) takes **no TLS config at all** — it is a multi-transport
|
|
||||||
accept-loop runner. TCP+TLS is an owned transport (via `with_tcp_tls`),
|
|
||||||
not an external loop:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl AlknetEndpoint {
|
impl AlknetEndpoint {
|
||||||
@@ -466,7 +432,9 @@ handle lives on the `TlsServerConfig`, not the endpoint.
|
|||||||
This resolves the single-`Arc<TlsServerConfig>` problem: the endpoint
|
This resolves the single-`Arc<TlsServerConfig>` problem: the endpoint
|
||||||
has no "the TLS config" to take because a hub has two. It also means
|
has no "the TLS config" to take because a hub has two. It also means
|
||||||
shutdown is single-owner — the endpoint owns all its accept loops
|
shutdown is single-owner — the endpoint owns all its accept loops
|
||||||
(quinn, iroh, TCP+TLS); one `shutdown()` stops them all.
|
(quinn, iroh, TCP+TLS); one `shutdown()` stops them all. See
|
||||||
|
[`crates/endpoint/README.md`](../endpoint/README.md) and
|
||||||
|
[ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md).
|
||||||
|
|
||||||
### The TCP+TLS accept loop (out of scope for this crate)
|
### The TCP+TLS accept loop (out of scope for this crate)
|
||||||
|
|
||||||
@@ -485,86 +453,74 @@ sharing, not transport accept logic.
|
|||||||
A hub dials out to workers it supervises and to other hubs
|
A hub dials out to workers it supervises and to other hubs
|
||||||
(hub-as-client); `alknet-worker` dials a hub. Both need a
|
(hub-as-client); `alknet-worker` dials a hub. Both need a
|
||||||
`rustls::ClientConfig` with ADR-034's verifier selection and ADR-084's
|
`rustls::ClientConfig` with ADR-034's verifier selection and ADR-084's
|
||||||
crypto provider. `TlsClientConfig` centralizes this — it is a
|
crypto provider. `TlsClientConfig` centralizes this, and is consumed by
|
||||||
present prerequisite for the first hub deployment, consumed by
|
|
||||||
`AlknetClient`'s QUIC and TCP+TLS dials (ADR-089).
|
`AlknetClient`'s QUIC and TCP+TLS dials (ADR-089).
|
||||||
|
|
||||||
There are exactly two clients in the alknet client surface as far as
|
There are exactly two clients in the alknet client surface as far as
|
||||||
`TlsClientConfig` and `AlknetClient` are concerned — **call**
|
`TlsClientConfig` and `AlknetClient` are concerned — **call**
|
||||||
(`CallClient`) and **channels** (`ChannelClient`, which is a proxy over
|
(`CallClient`) and **channels** (`ChannelClient`, which is a proxy over
|
||||||
many ALPNs via channel 0). Both must support all three transport
|
many ALPNs via channel 0). Both share `TlsClientConfig` via the dial;
|
||||||
accessors below; the TLS config is shared across them, the dial is
|
the TLS config is shared across them, the dial is per-transport
|
||||||
per-transport per-client.
|
per-client.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct TlsClientConfig {
|
pub struct TlsClientConfig {
|
||||||
config: rustls::ClientConfig,
|
rustls_config: rustls::ClientConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlsClientConfig {
|
impl TlsClientConfig {
|
||||||
/// Build a client TLS config. Takes two inputs, both derived from
|
/// Build a client TLS config from `ConnectionCredentials` and the
|
||||||
/// `Capabilities` (ADR-014) / `ConnectionCredentials`-shaped values
|
/// dial's ALPN. `ConnectionCredentials` (ADR-091, in `alknet-core`)
|
||||||
/// (ADR-091):
|
/// carries the two dimensions the dial consumes:
|
||||||
///
|
///
|
||||||
/// 1. `local_identity` — the local node's `TlsIdentity` (RFC 7250
|
/// 1. `tls_identity` — the local node's `TlsIdentity` (RFC 7250
|
||||||
/// raw key or X.509), presented as the client cert. `None` →
|
/// raw key or X.509), presented as the client cert. `None` →
|
||||||
/// no client cert (the server gets nothing to fingerprint).
|
/// no client cert (the server gets nothing to fingerprint).
|
||||||
/// `SelfSigned` → no client cert (dev-only). `Acme` →
|
/// `SelfSigned` → no client cert (dev-only). `Acme` →
|
||||||
/// `TlsError::AcmeConfig` (server-only identity).
|
/// `TlsError::AcmeConfig` (server-only identity).
|
||||||
///
|
///
|
||||||
/// 2. `verifier_context` — the inputs to ADR-034's server-cert
|
/// 2. `remote_identity` — the inputs to ADR-034's server-cert
|
||||||
/// verifier selection:
|
/// verifier selection:
|
||||||
/// - known peer (PeerEntry present) → fingerprint pin
|
/// - `Some(fingerprint)` (known peer, `PeerEntry` present) →
|
||||||
/// (FingerprintPinVerifier)
|
/// fingerprint pin (`FingerprintPinVerifier`)
|
||||||
/// - unknown remote + X.509 → CA verification
|
/// - `None` + X.509 transport → CA verification
|
||||||
/// (WebPkiServerVerifier)
|
/// (`WebPkiServerVerifier`)
|
||||||
/// - unknown remote + raw key → fail closed at handshake (not
|
/// - `None` + raw key → fail closed at handshake (not a `new`-
|
||||||
/// a `new`-time error; see ADR-088 §6)
|
/// time error; see ADR-088 §6)
|
||||||
///
|
///
|
||||||
/// Applies ADR-084 crypto provider (aws_lc_rs::default_provider()).
|
/// Applies ADR-084 crypto provider (aws_lc_rs::default_provider()).
|
||||||
pub fn new(
|
pub fn new(
|
||||||
local_identity: &Option<TlsIdentity>,
|
credentials: &ConnectionCredentials,
|
||||||
verifier_context: &ClientVerifierContext,
|
alpn: &[u8],
|
||||||
) -> Result<Self, TlsError>;
|
) -> Result<Self, TlsError>;
|
||||||
|
|
||||||
/// Produce a `quinn::ClientConfig` for a QUIC dial. Clones the
|
/// Consume the config and produce a `quinn::ClientConfig` for a
|
||||||
/// inner rustls config, wraps it in `QuicClientConfig`. Returns
|
/// QUIC dial. Returns `Result` because
|
||||||
/// `Result` because `QuicClientConfig::try_from(rustls::ClientConfig)`
|
/// `QuicClientConfig::try_from(rustls::ClientConfig)` can fail with
|
||||||
/// can fail with `NoInitialCipherSuite` — the same failure the
|
/// `NoInitialCipherSuite` — the same failure the server-side
|
||||||
/// server-side `for_quinn()` surfaces as `TlsError::QuinnWrap`.
|
/// `for_quinn()` surfaces as `TlsError::QuinnWrap`. Feature-gated
|
||||||
/// Feature-gated on `quinn`.
|
/// on `quinn`.
|
||||||
#[cfg(feature = "quinn")]
|
#[cfg(feature = "quinn")]
|
||||||
pub fn for_quinn(&self) -> Result<quinn::ClientConfig, TlsError>;
|
pub fn for_quinn(self) -> Result<quinn::ClientConfig, TlsError>;
|
||||||
|
|
||||||
/// Produce a `tokio_rustls::TlsConnector` for a TCP+TLS dial.
|
/// Consume the config and return the inner `rustls::ClientConfig`,
|
||||||
/// Clones the inner rustls config. Infallible —
|
/// for consumers that build their own transport-specific wrapper —
|
||||||
/// `TlsConnector::new(rustls::ClientConfig)` cannot fail.
|
/// e.g. `dial_tcp_tls` wraps it in a
|
||||||
/// Feature-gated on `tcp` (pulls `tokio-rustls`).
|
/// `tokio_rustls::TlsConnector::from(Arc::new(rustls_config))`. Not
|
||||||
#[cfg(feature = "tcp")]
|
/// feature-gated; the raw rustls config is transport-agnostic.
|
||||||
pub fn for_tcp_tls(&self) -> tokio_rustls::TlsConnector;
|
pub fn into_rustls_config(self) -> rustls::ClientConfig;
|
||||||
|
|
||||||
/// Borrow the underlying rustls config, for consumers that need to
|
|
||||||
/// build their own transport-specific wrapper not covered by
|
|
||||||
/// `for_quinn` / `for_tcp_tls` (e.g. a future transport). Not
|
|
||||||
/// feature-gated — returns the raw rustls config, not a
|
|
||||||
/// transport-specific wrapper.
|
|
||||||
pub fn rustls_config(&self) -> &rustls::ClientConfig;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `ClientVerifierContext` carries the inputs to ADR-034's verifier
|
`TlsClientConfig::new` runs ADR-034's verifier selection directly off
|
||||||
selection (whether a `PeerEntry` exists for the remote, the expected
|
`ConnectionCredentials.remote_identity` — there is no separate
|
||||||
fingerprint). The exact struct shape is an implementation detail; the
|
`ClientVerifierContext` type; the credential bundle carries the
|
||||||
decisions are in ADR-034. `ClientVerifierContext` is derived from
|
fingerprint (or its absence), which is all the verifier selection needs.
|
||||||
`ConnectionCredentials` (in `alknet-core`, per ADR-091) at the dial
|
The call-protocol `auth_token` is not in `ConnectionCredentials` — it
|
||||||
site — `AlknetClient` extracts the TLS-relevant fields
|
is a per-request field on `call.requested` payloads (a call-protocol /
|
||||||
(`local_identity` → client cert, `remote_identity` → fingerprint-pin
|
hub concept), not a transport credential; it never reaches
|
||||||
input) and builds a `ClientVerifierContext` from the latter. The
|
`TlsClientConfig`. The `TlsError` variant granularity (covering both
|
||||||
call-protocol `auth_token` is not in `ConnectionCredentials` — it is a
|
server and client errors) is decided — see
|
||||||
per-request field on `call.requested` payloads (a call-protocol / hub
|
|
||||||
concept), not a transport credential; it never reaches `TlsClientConfig`
|
|
||||||
or `ClientVerifierContext`. The `TlsError` variant granularity (covering
|
|
||||||
both server and client errors) is decided — see
|
|
||||||
[ADR-088](../../decisions/088-tlserror-shape.md) and the
|
[ADR-088](../../decisions/088-tlserror-shape.md) and the
|
||||||
[`TlsError`](#tlserror) section below.
|
[`TlsError`](#tlserror) section below.
|
||||||
|
|
||||||
@@ -576,25 +532,26 @@ containerized deployment with no system CA bundle), the built-in
|
|||||||
the `NoRootAnchors` failure mode unreachable in practice — a
|
the `NoRootAnchors` failure mode unreachable in practice — a
|
||||||
containerized worker dialing a public X.509 hub succeeds without
|
containerized worker dialing a public X.509 hub succeeds without
|
||||||
requiring the operator to mount a CA bundle. Native-certs *load* errors
|
requiring the operator to mount a CA bundle. Native-certs *load* errors
|
||||||
are logged, not returned (preserved behavior); the fallback guarantees
|
are logged, not returned; the fallback guarantees the store is
|
||||||
the store is non-empty regardless. See ADR-088 §5.
|
non-empty regardless. See ADR-088 §5.
|
||||||
|
|
||||||
`TlsClientConfig` produces a `rustls::ClientConfig`; the caller (the
|
`TlsClientConfig` produces a `rustls::ClientConfig`; the caller (the
|
||||||
transport-specific dial helper — `AlknetClient::dial_quic` /
|
transport-specific dial helper — `AlknetClient::dial_quic` /
|
||||||
`dial_tcp_tls`, ADR-089) passes it to the transport's connector. The
|
`dial_tcp_tls`, ADR-089) passes it to the transport's connector. The
|
||||||
config is transport-agnostic; the dial is not. This is the client-side
|
config is transport-agnostic; the dial is not. This is the client-side
|
||||||
analogue of ADR-065's server-side separation: the take-over
|
analogue of ADR-065's server-side separation: the take-over
|
||||||
(`spawn_dispatch` / `from_connection`, transport-agnostic) is built
|
(`spawn_dispatch` / `from_connection`, transport-agnostic) is
|
||||||
now; the dial (transport-specific) is per-transport. The
|
transport-agnostic; the dial (transport-specific) is per-transport.
|
||||||
transport-polymorphic dial is now extracted as `alknet-client`
|
The transport-polymorphic dial is `alknet-client` (ADR-089, resolves
|
||||||
(ADR-089, resolves OQ-55) — `AlknetClient` builds the `TlsClientConfig`
|
OQ-55) — `AlknetClient` builds the `TlsClientConfig` per-dial and
|
||||||
per-dial and calls the transport's connector.
|
calls the transport's connector.
|
||||||
|
|
||||||
The client-side accessor API mirrors the server side: `for_quinn()`
|
The client-side accessor API: `for_quinn()` (QUIC) and
|
||||||
/ `for_tcp_tls()` / `rustls_config()` — three transports, same
|
`into_rustls_config()` (any other transport — `dial_tcp_tls` wraps the
|
||||||
pattern. Iroh is the exception (see below). `AlknetClient` (ADR-089)
|
rustls config in a `TlsConnector`). Iroh is the exception (see below).
|
||||||
consumes `TlsClientConfig` via these accessors for the QUIC and TCP+TLS
|
`AlknetClient` (ADR-089) consumes `TlsClientConfig` via these accessors
|
||||||
dials; the iroh dial is the key-not-config exception.
|
for the QUIC and TCP+TLS dials; the iroh dial is the key-not-config
|
||||||
|
exception.
|
||||||
|
|
||||||
### Iroh — shares the key, not the config (client side too)
|
### Iroh — shares the key, not the config (client side too)
|
||||||
|
|
||||||
@@ -617,7 +574,16 @@ the `for_quinn()` accessors on both (`for_tcp_tls` is infallible —
|
|||||||
`alknet-tls`. The shape, the rationale for single-enum-over-thin-wrapper,
|
`alknet-tls`. The shape, the rationale for single-enum-over-thin-wrapper,
|
||||||
and the "what is NOT a variant" list are in
|
and the "what is NOT a variant" list are in
|
||||||
[ADR-088](../../decisions/088-tlserror-shape.md); this section is
|
[ADR-088](../../decisions/088-tlserror-shape.md); this section is
|
||||||
the sketch.
|
the target shape per that ADR.
|
||||||
|
|
||||||
|
> **Implementation note.** The current `alknet-tls/src/lib.rs`
|
||||||
|
> `TlsError` is a simplified 3-variant enum (`Config(String)`,
|
||||||
|
> `Io(io::Error)`, `Cert(String)`) without `#[non_exhaustive]`. The
|
||||||
|
> full ADR-088 shape below (six typed variants, `#[non_exhaustive]`,
|
||||||
|
> `#[from` sources) is the target; the present code folds the
|
||||||
|
> finer-grained categories into `Config`/`Cert` strings. An implementer
|
||||||
|
> refining `TlsError` to match ADR-088 is a two-way-door change (the
|
||||||
|
> enum is crate-local, no external match arms).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/// Errors produced by `TlsServerConfig::new`, `TlsClientConfig::new`,
|
/// Errors produced by `TlsServerConfig::new`, `TlsClientConfig::new`,
|
||||||
@@ -679,9 +645,9 @@ arrive asynchronously and are logged (ADR-082 §"Behavior-preservation
|
|||||||
invariants").
|
invariants").
|
||||||
|
|
||||||
**Ownership.** `TlsError` lives in `alknet-tls`, owned by the crate
|
**Ownership.** `TlsError` lives in `alknet-tls`, owned by the crate
|
||||||
that produces it. It is not re-exported from `alknet-core`; `EndpointError`
|
that produces it. It is not re-exported from `alknet-core`; there is no
|
||||||
is removed entirely after ADR-083 (both variants were vestigial), so
|
`EndpointError` (removed per ADR-083 — both variants were vestigial),
|
||||||
core has no endpoint error type and does not need to know about
|
so core has no endpoint error type and does not need to know about
|
||||||
`TlsError`. The assembly layer (hub/worker) depends on `alknet-tls`
|
`TlsError`. The assembly layer (hub/worker) depends on `alknet-tls`
|
||||||
directly and gets `TlsError` from that dependency.
|
directly and gets `TlsError` from that dependency.
|
||||||
|
|
||||||
@@ -689,15 +655,15 @@ directly and gets `TlsError` from that dependency.
|
|||||||
|
|
||||||
```
|
```
|
||||||
alknet-tls
|
alknet-tls
|
||||||
├── alknet-core (TlsIdentity, Ed25519SecretKey, fingerprint)
|
└── alknet-core (TlsIdentity, Ed25519SecretKey, fingerprint)
|
||||||
|
|
||||||
alknet-core (loses TLS setup code + endpoint)
|
alknet-core (lightweight — types + auth + config + fingerprint + credentials)
|
||||||
├── (rustls — only for fingerprint.rs types, if kept)
|
└── (rustls / rustls-pki-types — only for fingerprint.rs types)
|
||||||
|
|
||||||
alknet-call (pure protocol crate — no TLS/transport deps per ADR-089 §5)
|
alknet-call (pure protocol crate — no TLS/transport deps per ADR-089 §5)
|
||||||
└── alknet-core (ProtocolHandler, Connection, types; ConnectionCredentials/
|
└── alknet-core (ProtocolHandler, Connection, types; ConnectionCredentials/
|
||||||
RemoteIdentity moved to core per ADR-091; CallCredentials removed per ADR-091 Am. 2026-07-17
|
RemoteIdentity from core per ADR-091; CallCredentials removed per
|
||||||
alknet-call)
|
ADR-091 Am. 2026-07-17)
|
||||||
|
|
||||||
alknet-hub (multi-transport endpoint)
|
alknet-hub (multi-transport endpoint)
|
||||||
├── alknet-tls (TlsServerConfig — shared across quinn + TCP)
|
├── alknet-tls (TlsServerConfig — shared across quinn + TCP)
|
||||||
@@ -706,7 +672,7 @@ alknet-hub (multi-transport endpoint)
|
|||||||
├── alknet-channels-call (ChannelClient)
|
├── alknet-channels-call (ChannelClient)
|
||||||
├── alknet-call (CallAdapter, Dispatcher)
|
├── alknet-call (CallAdapter, Dispatcher)
|
||||||
├── alknet-http (HttpAdapter)
|
├── alknet-http (HttpAdapter)
|
||||||
├── alknet-core (Connection, ProtocolHandler, AuthContext, IdentityProvider)
|
└── alknet-core (Connection, ProtocolHandler, AuthContext, IdentityProvider)
|
||||||
```
|
```
|
||||||
|
|
||||||
`alknet-tls` depends on `alknet-core` only. No handler crate depends on
|
`alknet-tls` depends on `alknet-core` only. No handler crate depends on
|
||||||
@@ -725,7 +691,7 @@ All design decisions are documented as ADRs in
|
|||||||
| ADR | Decision | Summary |
|
| ADR | Decision | Summary |
|
||||||
|-----|----------|---------|
|
|-----|----------|---------|
|
||||||
| [082](../../decisions/082-alknet-tls-extraction.md) | alknet-tls crate extraction | Extract TLS setup from alknet-core/endpoint.rs; `TlsServerConfig` shareable across quinn + TCP+TLS + iroh; one ACME state machine |
|
| [082](../../decisions/082-alknet-tls-extraction.md) | alknet-tls crate extraction | Extract TLS setup from alknet-core/endpoint.rs; `TlsServerConfig` shareable across quinn + TCP+TLS + iroh; one ACME state machine |
|
||||||
| [083](../../decisions/083-endpoint-as-accept-loop-runner.md) | Endpoint as multi-transport accept-loop runner | `AlknetEndpoint` takes no TLS config; TCP+TLS is an owned transport (`with_tcp_tls`); `dispatch` public for SSH/WT; `acme-tls/1` guard moves to shared `dispatch` |
|
| [083](../../decisions/083-endpoint-as-accept-loop-runner.md) | Endpoint as multi-transport accept-loop runner | `AlknetEndpoint` takes no TLS config; TCP+TLS is an owned transport (`with_tcp_tls`); `dispatch` public for SSH/WT; `acme-tls/1` guard is in shared `dispatch` |
|
||||||
| [084](../../decisions/084-aws-lc-rs-crypto-provider.md) | aws-lc-rs crypto provider | `rustls::crypto::aws_lc_rs::default_provider()` on all server + client config paths; matches iroh; FIPS-capable; do not switch to `ring` or process-default without a new ADR |
|
| [084](../../decisions/084-aws-lc-rs-crypto-provider.md) | aws-lc-rs crypto provider | `rustls::crypto::aws_lc_rs::default_provider()` on all server + client config paths; matches iroh; FIPS-capable; do not switch to `ring` or process-default without a new ADR |
|
||||||
| [086](../../decisions/086-endpoint-types-and-entry-points.md) | Endpoint types and entry points | Three endpoint types (web/native/iroh); split ALPN lists per endpoint type (resolves OQ-62); entry-point vs. endpoint ALPN distinction |
|
| [086](../../decisions/086-endpoint-types-and-entry-points.md) | Endpoint types and entry points | Three endpoint types (web/native/iroh); split ALPN lists per endpoint type (resolves OQ-62); entry-point vs. endpoint ALPN distinction |
|
||||||
| [087](../../decisions/087-tlsclientconfig-not-blocked-on-dial.md) | `TlsClientConfig` not blocked on dial seam | `alknet-tls` provides `TlsClientConfig` (client-side); not deferred behind OQ-55; breaks the circular hedge; hub-as-client is a first-class use case |
|
| [087](../../decisions/087-tlsclientconfig-not-blocked-on-dial.md) | `TlsClientConfig` not blocked on dial seam | `alknet-tls` provides `TlsClientConfig` (client-side); not deferred behind OQ-55; breaks the circular hedge; hub-as-client is a first-class use case |
|
||||||
@@ -773,38 +739,35 @@ See [open-questions.md](../../open-questions.md) for full details.
|
|||||||
- **OQ-64** (resolved): `alknet-tls` provides `TlsClientConfig`
|
- **OQ-64** (resolved): `alknet-tls` provides `TlsClientConfig`
|
||||||
(ADR-087). Not blocked on the dial-seam extraction — the TLS
|
(ADR-087). Not blocked on the dial-seam extraction — the TLS
|
||||||
config is a prerequisite for the dial, not a consequence of it.
|
config is a prerequisite for the dial, not a consequence of it.
|
||||||
Centralizes ADR-034 verifier selection + ADR-084 provider; the
|
Centralizes ADR-034 verifier selection + ADR-084 provider. The dial
|
||||||
hub-as-client requirement makes it a prerequisite for the first hub
|
seam is `alknet-client` (ADR-089, OQ-55 resolved); `TlsClientConfig`
|
||||||
deployment. The dial seam is now extracted as `alknet-client`
|
is consumed by `AlknetClient`'s QUIC and TCP+TLS dials.
|
||||||
(ADR-089, OQ-55 resolved); `TlsClientConfig` is consumed by
|
|
||||||
`AlknetClient`'s QUIC and TCP+TLS dials.
|
|
||||||
|
|
||||||
- **OQ-55** (resolved by ADR-089): `AlknetClient::dial()` — the
|
- **OQ-55** (resolved by ADR-089): `AlknetClient::dial()` — the
|
||||||
transport-polymorphic dial seam. Extracted as a new crate
|
transport-polymorphic dial seam. `alknet-client` has three dial
|
||||||
`alknet-client` with three dial methods (`dial_quic` /
|
methods (`dial_quic` / `dial_tcp_tls` / `dial_iroh`).
|
||||||
`dial_tcp_tls` / `dial_iroh`). `TlsClientConfig` (OQ-64, resolved)
|
`TlsClientConfig` (OQ-64, resolved) is the prerequisite the dial
|
||||||
is the prerequisite the dial consumes. See
|
consumes. See [`crates/client/README.md`](../client/README.md) and
|
||||||
[`crates/client/README.md`](../client/README.md) and
|
|
||||||
[ADR-089](../../decisions/089-alknetclient-native-dial-seam.md).
|
[ADR-089](../../decisions/089-alknetclient-native-dial-seam.md).
|
||||||
|
|
||||||
### Next session — client shape
|
### Client shape (in `alknet-client`)
|
||||||
|
|
||||||
The client is now specced. [`crates/client/README.md`](../client/README.md)
|
[`crates/client/README.md`](../client/README.md) defines `AlknetClient`
|
||||||
defines `AlknetClient` — the native client dial seam (ADR-089, resolves
|
— the native client dial seam (ADR-089, resolves OQ-55). There are
|
||||||
OQ-55). There are exactly two clients in the alknet client surface as
|
exactly two clients in the alknet client surface as far as
|
||||||
far as `TlsClientConfig` and `AlknetClient` are concerned: **call**
|
`TlsClientConfig` and `AlknetClient` are concerned: **call**
|
||||||
(`CallClient`) and **channels** (`ChannelClient`, a proxy over many
|
(`CallClient`) and **channels** (`ChannelClient`, a proxy over many
|
||||||
ALPNs via channel 0). Both consume `TlsClientConfig` via the same three
|
ALPNs via channel 0). Both consume `TlsClientConfig` through the dial
|
||||||
accessors (`for_quinn`, `for_tcp_tls`, `rustls_config`); iroh is the
|
(`for_quinn` for QUIC, `into_rustls_config` wrapped in a `TlsConnector`
|
||||||
exception (shares the key, not the config). `AlknetClient` is the dial
|
for TCP+TLS); iroh is the exception (shares the key, not the config).
|
||||||
that feeds them — it produces a `Connection` and the protocol
|
`AlknetClient` is the dial that feeds them — it produces a `Connection`
|
||||||
take-overs (`spawn_dispatch`, `from_connection`) consume it. The
|
and the protocol take-overs (`spawn_dispatch`, `from_connection`)
|
||||||
per-protocol QUIC convenience constructors (`CallClient::connect` /
|
consume it. The per-protocol QUIC convenience constructors
|
||||||
`ChannelClient::connect_quic`) are **removed** per ADR-089 §5 — the
|
(`CallClient::connect` / `ChannelClient::connect_quic`) are removed
|
||||||
dial is centralized in `AlknetClient`, and the protocol crates shed
|
per ADR-089 §5 — the dial is centralized in `AlknetClient`, and the
|
||||||
their TLS/transport deps. The `alknet/register` ALPN (native
|
protocol crates have no TLS/transport deps. The `alknet/register` ALPN
|
||||||
registration entry point, parallel to HTTP registration in OQ-58) is
|
(native registration entry point, parallel to HTTP registration in
|
||||||
named by ADR-089; its wire protocol is deferred (OQ-66).
|
OQ-58) is named by ADR-089; its wire protocol is deferred (OQ-66).
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
@@ -829,15 +792,14 @@ named by ADR-089; its wire protocol is deferred (OQ-66).
|
|||||||
(the endpoint spec; TLS config is built by `alknet-tls`, not the
|
(the endpoint spec; TLS config is built by `alknet-tls`, not the
|
||||||
endpoint — per ADR-083)
|
endpoint — per ADR-083)
|
||||||
- `docs/architecture/crates/core/config.md` — `TlsIdentity`, `StaticConfig`
|
- `docs/architecture/crates/core/config.md` — `TlsIdentity`, `StaticConfig`
|
||||||
- `crates/alknet-core/src/endpoint.rs` — the server-side code being
|
- `crates/alknet-tls/src/server.rs` — `TlsServerConfig`,
|
||||||
extracted (`build_rustls_server_config`, `TlsSetup`, `RawKeyCertResolver`,
|
`RawKeyCertResolver`, `AcceptAnyCertVerifier`,
|
||||||
`Ed25519SigningKey`, `AcceptAnyCertVerifier`, `generate_self_signed_cert`,
|
`generate_self_signed_cert`, `build_rustls_server_config`
|
||||||
`load_cert_chain`, `load_private_key`)
|
- `crates/alknet-tls/src/client.rs` — `TlsClientConfig`,
|
||||||
- `crates/alknet-call/src/client/call_client.rs` — the client-side code
|
|
||||||
being extracted (`build_quinn_client_config`, `build_client_auth`,
|
|
||||||
`select_server_verifier`, `load_platform_root_cert_store`,
|
|
||||||
`FingerprintPinVerifier`, `RawKeyClientCertResolver`,
|
`FingerprintPinVerifier`, `RawKeyClientCertResolver`,
|
||||||
`NoClientCertResolver`, `Ed25519SigningKey` (duplicate),
|
`NoClientCertResolver`, `select_server_verifier`, `build_client_auth`,
|
||||||
`load_cert_chain`/`load_private_key` (duplicates))
|
`load_platform_root_cert_store`
|
||||||
|
- `crates/alknet-tls/src/pem.rs` — `load_cert_chain`, `load_private_key`
|
||||||
|
- `crates/alknet-tls/src/signing.rs` — `Ed25519SigningKey`
|
||||||
- `crates/alknet-core/src/fingerprint.rs` — fingerprint extraction
|
- `crates/alknet-core/src/fingerprint.rs` — fingerprint extraction
|
||||||
(shared by server endpoint and client verifier)
|
(shared by server endpoint and client verifier)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-16
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Open Questions
|
# Open Questions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-07-15
|
last_updated: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alknet Overview
|
# Alknet Overview
|
||||||
@@ -97,11 +97,12 @@ alknet-vault (standalone — foundational to ACL: key derivation, identity)
|
|||||||
│ alknet-core ProtocolHandler, Connection, BidiStreamSource, AuthContext,
|
│ alknet-core ProtocolHandler, Connection, BidiStreamSource, AuthContext,
|
||||||
│ │ IdentityProvider, StaticConfig, DynamicConfig, fingerprint,
|
│ │ IdentityProvider, StaticConfig, DynamicConfig, fingerprint,
|
||||||
│ │ ConnectionCredentials, RemoteIdentity
|
│ │ ConnectionCredentials, RemoteIdentity
|
||||||
│ │ (endpoint extracted to alknet-endpoint; core is now lightweight
|
│ │ (lightweight types+auth+config+credentials; endpoint in
|
||||||
│ │ types+auth+config — no quinn/iroh/rcgen deps; ConnectionCredentials
|
│ │ alknet-endpoint; no rcgen/rustls-pemfile/rustls-acme deps;
|
||||||
│ │ + RemoteIdentity moved here from alknet-call per ADR-091;
|
│ │ quinn/iroh stay for Connection::from_quinn/from_iroh;
|
||||||
|
│ │ ConnectionCredentials + RemoteIdentity per ADR-091;
|
||||||
│ │ CallCredentials removed per ADR-091 Am. 2026-07-17)
|
│ │ CallCredentials removed per ADR-091 Am. 2026-07-17)
|
||||||
│ ├── alknet-tls TlsServerConfig + TlsClientConfig + FingerprintPinVerifier — shared TLS config across quinn + TCP+TLS + iroh (ADR-082/087; FingerprintPinVerifier moved from alknet-call per ADR-089 §5)
|
│ ├── alknet-tls TlsServerConfig + TlsClientConfig + FingerprintPinVerifier — shared TLS config across quinn + TCP+TLS + iroh (ADR-082/087; FingerprintPinVerifier per ADR-089 §5)
|
||||||
│ ├── alknet-call CallAdapter on alknet/call, CallClient (spawn_dispatch only — connect removed per ADR-089 §5), OperationRegistry, adapters (no TLS/transport deps)
|
│ ├── alknet-call CallAdapter on alknet/call, CallClient (spawn_dispatch only — connect removed per ADR-089 §5), OperationRegistry, adapters (no TLS/transport deps)
|
||||||
│ ├── alknet-channels
|
│ ├── alknet-channels
|
||||||
│ │ ├── alknet-channels-core pure multiplexer (wire format, demux/mux) — ADR-081
|
│ │ ├── alknet-channels-core pure multiplexer (wire format, demux/mux) — ADR-081
|
||||||
|
|||||||
Reference in new issue
Block a user