tasks: decompose phases 0-1 of crate extraction into implementation tasks
Phase 0 (core/connection-credentials): purely additive — add ConnectionCredentials + RemoteIdentity to alknet-core. No call crate changes. ~40 lines, zero breakage. Phase 1 (tls/*): greenfield alknet-tls crate in 5 tasks: - tls/crate-init: Cargo.toml, deps, module skeleton - tls/server-extract: TlsServerConfig + server TLS code from endpoint.rs - tls/client-extract: TlsClientConfig + client TLS code from call_client.rs - tls/tests: 32 TLS tests moved and adapted - tls/review-tls: phase gate review checkpoint All old code stays duplicated — purely additive phases. Prunes in 4-5.
This commit is contained in:
1 parent
e91d943857
commit
4ced71f44a
6 files changed
+815
No files matched your search
@@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
id: core/connection-credentials
|
||||||
|
name: Add ConnectionCredentials + RemoteIdentity to alknet-core (purely additive)
|
||||||
|
status: pending
|
||||||
|
depends_on: []
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 0 of the crate extraction (per `docs/research/alknet-crate-extraction/findings.md`).
|
||||||
|
Add `ConnectionCredentials` + `RemoteIdentity` to a new `crates/alknet-core/src/credentials.rs`.
|
||||||
|
This is **purely additive** — core gains two small types, nothing else changes, no breakage.
|
||||||
|
|
||||||
|
`ConnectionCredentials` is the transport-level credential bundle (ADR-091) — it carries
|
||||||
|
`tls_identity` + `remote_identity` (the two dimensions the dial consumes). It is the
|
||||||
|
transport-level equivalent of `CallCredentials` (which lives in `alknet-call` and carries
|
||||||
|
an additional `auth_token` field). `ConnectionCredentials` is what `alknet-client` (Phase 3)
|
||||||
|
and `alknet-tls` (Phase 1) will consume — the dep graph is clean from the start, with no
|
||||||
|
temporary dep on `alknet-call`.
|
||||||
|
|
||||||
|
`alknet-call` is **not touched** in this phase. Its `CallCredentials` + `RemoteIdentity`
|
||||||
|
stay as-is. The call crate refactor (removing `CallCredentials`, importing from core,
|
||||||
|
moving tests) happens in Phase 5 when the full prune is done. This keeps Phase 0 truly
|
||||||
|
additive and avoids touching tests that may be removed later.
|
||||||
|
|
||||||
|
### Step 1: Create `crates/alknet-core/src/credentials.rs`
|
||||||
|
|
||||||
|
New file with two types:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Transport-level credential bundle for outbound connections (ADR-091).
|
||||||
|
//!
|
||||||
|
//! `ConnectionCredentials` carries the two dimensions the dial consumes:
|
||||||
|
//! the local node's TLS identity and the expected remote identity.
|
||||||
|
//! It is transport-agnostic — consumed by `alknet-tls` (TLS setup) and
|
||||||
|
//! `alknet-client` (dial).
|
||||||
|
|
||||||
|
use crate::config::TlsIdentity;
|
||||||
|
|
||||||
|
/// Expected identity of the remote node (ADR-017 §7, extended by 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.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RemoteIdentity {
|
||||||
|
pub fingerprint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Credentials for an outbound connection (ADR-091). All dimensions come from
|
||||||
|
/// `Capabilities` (ADR-014), never from environment variables — see the
|
||||||
|
/// No-Env-Vars Invariant in
|
||||||
|
/// `docs/architecture/crates/call/client-and-adapters.md`.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ConnectionCredentials {
|
||||||
|
/// The local node's TLS identity (RFC 7250 raw key or X.509), derived
|
||||||
|
/// from the vault at startup.
|
||||||
|
pub tls_identity: Option<TlsIdentity>,
|
||||||
|
/// Expected fingerprint/cert of the remote node, stored as a capability.
|
||||||
|
/// `Some` → fingerprint pin (known peer with a `PeerEntry`); `None` → CA
|
||||||
|
/// verification for X.509 remotes, fail-closed for Ed25519 raw-key remotes
|
||||||
|
/// (ADR-034 §2/§3). `None` is the public-X.509-endpoint state, not a
|
||||||
|
/// missing field — must not be defaulted to a placeholder.
|
||||||
|
pub remote_identity: Option<RemoteIdentity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionCredentials {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_tls_identity(mut self, tls_identity: TlsIdentity) -> Self {
|
||||||
|
self.tls_identity = Some(tls_identity);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_remote_identity(mut self, remote: RemoteIdentity) -> Self {
|
||||||
|
self.remote_identity = Some(remote);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_credentials_builder_methods() {
|
||||||
|
let creds = ConnectionCredentials::new().with_remote_identity(RemoteIdentity {
|
||||||
|
fingerprint: "SHA256:abc".to_string(),
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
creds.remote_identity.as_ref().unwrap().fingerprint,
|
||||||
|
"SHA256:abc"
|
||||||
|
);
|
||||||
|
assert!(creds.tls_identity.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_credentials_none_is_load_bearing_not_defaulted() {
|
||||||
|
let creds = ConnectionCredentials::new();
|
||||||
|
assert!(
|
||||||
|
creds.remote_identity.is_none(),
|
||||||
|
"ConnectionCredentials::new() must keep remote_identity as None (the load-bearing \
|
||||||
|
public-X.509-endpoint state), not default it to a placeholder"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Update `alknet-core/src/lib.rs`
|
||||||
|
|
||||||
|
Add `pub mod credentials;` and re-export the types:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub mod credentials;
|
||||||
|
// ... existing modules ...
|
||||||
|
|
||||||
|
pub use credentials::{ConnectionCredentials, RemoteIdentity};
|
||||||
|
```
|
||||||
|
|
||||||
|
### What does NOT change
|
||||||
|
|
||||||
|
- `alknet-call` — completely untouched. `CallCredentials` + `RemoteIdentity` stay as-is.
|
||||||
|
- `endpoint.rs` — unchanged.
|
||||||
|
- All existing tests — unchanged.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `crates/alknet-core/src/credentials.rs` exists with `ConnectionCredentials` + `RemoteIdentity` as specified
|
||||||
|
- [ ] `ConnectionCredentials` has `tls_identity` and `remote_identity` fields (no `auth_token`)
|
||||||
|
- [ ] `ConnectionCredentials` has `new()`, `with_tls_identity()`, `with_remote_identity()` builder methods
|
||||||
|
- [ ] `alknet-core/src/lib.rs` has `pub mod credentials;` and re-exports both types
|
||||||
|
- [ ] Unit tests for `ConnectionCredentials` builder and `None`-is-load-bearing invariant pass
|
||||||
|
- [ ] `cargo test -p alknet-core` passes (all feature combos)
|
||||||
|
- [ ] `cargo test --workspace` passes (no regressions)
|
||||||
|
- [ ] `cargo clippy --workspace` passes with no warnings
|
||||||
|
- [ ] `cargo fmt --check --workspace` passes
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/alknet-crate-extraction/findings.md — Phase 0
|
||||||
|
- docs/architecture/decisions/091-connection-credentials.md — ADR-091 (amended 2026-07-17)
|
||||||
|
- docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md — ADR-034
|
||||||
|
- crates/alknet-call/src/client/call_client.rs — current `CallCredentials` + `RemoteIdentity` definitions (reference for field shapes)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is Phase 0 of the crate extraction — the smallest and most independent
|
||||||
|
> phase. ~40 lines of new code in core, zero changes anywhere else. Purely
|
||||||
|
> additive — `alknet-call` is not touched. The field name stays `tls_identity`
|
||||||
|
> (not `local_identity`) to match the existing code and avoid unnecessary churn
|
||||||
|
> — the rename can happen later if desired. `ConnectionCredentials` has no
|
||||||
|
> `auth_token` field because `auth_token` is a per-request payload field, not a
|
||||||
|
> transport-level credential (ADR-091). The call crate refactor (removing
|
||||||
|
> `CallCredentials`, importing from core, moving tests) happens in Phase 5.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
---
|
||||||
|
id: tls/client-extract
|
||||||
|
name: Extract client-side TLS code from alknet-call/call_client.rs into alknet-tls
|
||||||
|
status: pending
|
||||||
|
depends_on: [tls/server-extract]
|
||||||
|
scope: narrow
|
||||||
|
risk: medium
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 1, Task 3 of the crate extraction. Extract the client-side TLS setup code from
|
||||||
|
`crates/alknet-call/src/client/call_client.rs` (lines 189-320) into
|
||||||
|
`crates/alknet-tls/src/client.rs`. Reuse the shared `Ed25519SigningKey` from
|
||||||
|
`signing.rs` and `load_cert_chain`/`load_private_key` from `pem.rs` (already extracted
|
||||||
|
in the previous task).
|
||||||
|
|
||||||
|
The old code **stays** in `call_client.rs` (duplicated) — no breakage. The new crate is
|
||||||
|
self-contained and builds standalone.
|
||||||
|
|
||||||
|
### Types to extract
|
||||||
|
|
||||||
|
From `call_client.rs` lines 189-320:
|
||||||
|
|
||||||
|
| Type/Function | Lines | Destination |
|
||||||
|
|---------------|-------|-------------|
|
||||||
|
| `build_quinn_client_config()` | 189-211 | `client.rs` |
|
||||||
|
| `build_client_auth()` | 213-246 | `client.rs` |
|
||||||
|
| `select_server_verifier()` | 248-278 | `client.rs` |
|
||||||
|
| `load_platform_root_cert_store()` | 280-297 | `client.rs` |
|
||||||
|
| `load_cert_chain()` | 299-308 | **skip** — already in `pem.rs` from server-extract |
|
||||||
|
| `load_private_key()` | 310-321 | **skip** — already in `pem.rs` from server-extract |
|
||||||
|
|
||||||
|
Also extract from `call_client.rs` lines 323-567 (the struct impls):
|
||||||
|
|
||||||
|
| Type/Function | Lines | Destination |
|
||||||
|
|---------------|-------|-------------|
|
||||||
|
| `RawKeyClientCertResolver` struct + impls | 323-374 | `client.rs` |
|
||||||
|
| `NoClientCertResolver` struct + impls | 376-403 | `client.rs` |
|
||||||
|
| `FingerprintPinVerifier` struct + impls | 405-507 | `client.rs` |
|
||||||
|
| `Ed25519SigningKey` struct + impls | 509-567 | **skip** — already in `signing.rs` from server-extract |
|
||||||
|
|
||||||
|
### New public API type
|
||||||
|
|
||||||
|
Wrap the extracted client-side code in a public API type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// client.rs
|
||||||
|
|
||||||
|
/// Client-side TLS configuration, transport-agnostic.
|
||||||
|
/// Wraps a `rustls::ClientConfig` built from `ConnectionCredentials`.
|
||||||
|
pub struct TlsClientConfig {
|
||||||
|
pub(crate) rustls_config: rustls::ClientConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsClientConfig {
|
||||||
|
/// Build a client config from `ConnectionCredentials` and an ALPN.
|
||||||
|
/// Selects the server cert verifier by `remote_identity` presence
|
||||||
|
/// (ADR-034 §3): `Some` → fingerprint pin, `None` → CA verification.
|
||||||
|
pub fn new(
|
||||||
|
credentials: &alknet_core::credentials::ConnectionCredentials,
|
||||||
|
alpn: &[u8],
|
||||||
|
) -> Result<Self, TlsError> { ... }
|
||||||
|
|
||||||
|
/// Convert to a `quinn::ClientConfig` for QUIC transport.
|
||||||
|
#[cfg(feature = "quinn")]
|
||||||
|
pub fn for_quinn(self) -> Result<quinn::ClientConfig, TlsError> { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adaptations
|
||||||
|
|
||||||
|
1. **Error types**: Replace `String` error returns with `TlsError`. The current code returns
|
||||||
|
`Result<_, String>` from most functions — convert to `Result<_, TlsError>`.
|
||||||
|
2. **Imports**: Update `alknet_core::config::*` and `alknet_core::fingerprint::*` imports.
|
||||||
|
Use `crate::signing::Ed25519SigningKey` (not a local copy).
|
||||||
|
Use `crate::pem::load_cert_chain` / `crate::pem::load_private_key` (not local copies).
|
||||||
|
3. **`load_platform_root_cert_store`**: Add the `webpki-roots` fallback (ADR-088 §5) —
|
||||||
|
when the platform store is empty, merge built-in `webpki-roots` so `NoRootAnchors` is
|
||||||
|
unreachable in practice. This is new code, not extracted.
|
||||||
|
4. **`FingerprintPinVerifier`**: The `verify_tls12_signature` and `verify_tls13_signature`
|
||||||
|
methods use `alknet_core::fingerprint::extract_ed25519_raw_key_from_spki` — keep that
|
||||||
|
import.
|
||||||
|
5. **Feature gates**: All client-side TLS code is gated on `#[cfg(feature = "quinn")]`.
|
||||||
|
The `TlsClientConfig::new()` constructor itself is **not** feature-gated (it builds a
|
||||||
|
`rustls::ClientConfig`, which is transport-agnostic). Only `for_quinn()` is gated.
|
||||||
|
|
||||||
|
### What stays in call
|
||||||
|
|
||||||
|
The old code in `call_client.rs` lines 189-567 is **not deleted** — it stays as a duplicate.
|
||||||
|
The prune happens in Phase 5. This task only adds code to `alknet-tls`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `crates/alknet-tls/src/client.rs` contains `TlsClientConfig`, `build_quinn_client_config` (as `TlsClientConfig::new`), `build_client_auth`, `select_server_verifier`, `load_platform_root_cert_store`, `FingerprintPinVerifier`, `RawKeyClientCertResolver`, `NoClientCertResolver`
|
||||||
|
- [ ] `TlsClientConfig::new()` accepts `&ConnectionCredentials` + `&[u8]` and returns `Result<Self, TlsError>`
|
||||||
|
- [ ] `TlsClientConfig::for_quinn()` converts to `quinn::ClientConfig` (feature-gated)
|
||||||
|
- [ ] `load_platform_root_cert_store` includes `webpki-roots` fallback (ADR-088 §5)
|
||||||
|
- [ ] Client code uses `crate::signing::Ed25519SigningKey` (not a local copy)
|
||||||
|
- [ ] Client code uses `crate::pem::load_cert_chain` / `crate::pem::load_private_key` (not local copies)
|
||||||
|
- [ ] All error returns use `TlsError` (not `String`)
|
||||||
|
- [ ] Feature gates correct: `quinn` for `for_quinn()` and quinn-specific helpers
|
||||||
|
- [ ] `cargo check -p alknet-tls` succeeds (all feature combos)
|
||||||
|
- [ ] `cargo clippy -p alknet-tls` succeeds with no warnings
|
||||||
|
- [ ] `cargo test -p alknet-core` still passes (old code untouched)
|
||||||
|
- [ ] `cargo test -p alknet-call` still passes (old code untouched)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/alknet-crate-extraction/findings.md — Phase 1, client-side extraction
|
||||||
|
- docs/architecture/decisions/088-webpki-roots-fallback.md — ADR-088 §5
|
||||||
|
- docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md — ADR-034 §3
|
||||||
|
- crates/alknet-call/src/client/call_client.rs — lines 189-567 (source code to extract)
|
||||||
|
- crates/alknet-core/src/fingerprint.rs — `extract_ed25519_raw_key_from_spki`, `fingerprint_from_cert_der`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is the smaller extraction (~130 lines of implementation). The main work
|
||||||
|
> is adapting error types (String → TlsError) and reusing the shared
|
||||||
|
> `Ed25519SigningKey` and `load_cert_chain`/`load_private_key` from the
|
||||||
|
> server-extract task. The `webpki-roots` fallback in `load_platform_root_cert_store`
|
||||||
|
> is new code (not extracted) per ADR-088 §5. The old code in `call_client.rs` is
|
||||||
|
> NOT deleted — that's Phase 5.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
id: tls/crate-init
|
||||||
|
name: Initialize alknet-tls crate with Cargo.toml, dependencies, and module skeleton
|
||||||
|
status: pending
|
||||||
|
depends_on: [core/connection-credentials]
|
||||||
|
scope: moderate
|
||||||
|
risk: low
|
||||||
|
impact: project
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 1, Task 1 of the crate extraction (per `docs/research/alknet-crate-extraction/findings.md`).
|
||||||
|
Initialize the `alknet-tls` crate from scratch. This crate provides TLS setup types for both
|
||||||
|
server-side and client-side — `TlsServerConfig`, `TlsClientConfig`, `TlsError`, and the shared
|
||||||
|
TLS helpers (`Ed25519SigningKey`, cert/key loaders, verifiers, cert resolvers).
|
||||||
|
|
||||||
|
### Crate setup
|
||||||
|
|
||||||
|
Create `crates/alknet-tls/` with:
|
||||||
|
|
||||||
|
- `Cargo.toml` — package metadata, dependencies, feature flags
|
||||||
|
- `src/lib.rs` — crate root with module declarations and re-exports
|
||||||
|
- Module skeleton files for:
|
||||||
|
- `src/server.rs` — `TlsServerConfig`, `build_rustls_server_config`, `RawKeyCertResolver`, `AcceptAnyCertVerifier`, `SelfSignedCert`, `generate_self_signed_cert`, `TlsSetup` (extracted from `endpoint.rs` lines 493-934)
|
||||||
|
- `src/client.rs` — `TlsClientConfig`, `build_quinn_client_config`, `build_client_auth`, `select_server_verifier`, `FingerprintPinVerifier`, `RawKeyClientCertResolver`, `NoClientCertResolver`, `load_platform_root_cert_store` (extracted from `call_client.rs` lines 189-320)
|
||||||
|
- `src/signing.rs` — `Ed25519SigningKey` (consolidated — one copy, used by both server + client)
|
||||||
|
- `src/pem.rs` — `load_cert_chain`, `load_private_key` (consolidated — one copy, used by both server + client)
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
Per the findings (Phase 1):
|
||||||
|
|
||||||
|
| Crate | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `alknet-core` | `TlsIdentity`, `Ed25519SecretKey`, `fingerprint` (workspace path) |
|
||||||
|
| `rustls` 0.23 | TLS implementation (aws-lc-rs) |
|
||||||
|
| `rustls-pemfile` 2 | PEM cert/key loading |
|
||||||
|
| `rustls-native-certs` 0.8 | Platform root CA store |
|
||||||
|
| `webpki-roots` | Built-in root CA fallback (new — not extracted) |
|
||||||
|
| `rcgen` 0.13 | Self-signed cert generation |
|
||||||
|
| `tokio` 1 (full) | Async runtime |
|
||||||
|
| `quinn` 0.11 | QUIC transport (optional, feature-gated) |
|
||||||
|
| `tokio-rustls` 0.26 | TCP+TLS transport (optional, feature-gated) |
|
||||||
|
| `rustls-acme` 0.12 | ACME cert provisioning (optional, feature-gated) |
|
||||||
|
| `tracing` 0.1 | Structured logging |
|
||||||
|
| `thiserror` 2 | Error enums |
|
||||||
|
|
||||||
|
`rustls-native-certs` and `webpki-roots` are **always-present (not feature-gated)** — the
|
||||||
|
unknown-X.509-remote CA-verification path in `TlsClientConfig::new` is transport-agnostic;
|
||||||
|
the `webpki-roots` fallback merges built-in roots when the platform store is empty so
|
||||||
|
`NoRootAnchors` is unreachable in practice (ADR-088 §5).
|
||||||
|
|
||||||
|
### Feature flags
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[features]
|
||||||
|
default = ["quinn"]
|
||||||
|
quinn = ["dep:quinn"]
|
||||||
|
tcp = ["dep:tokio-rustls"]
|
||||||
|
acme = ["dep:rustls-acme"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workspace Cargo.toml
|
||||||
|
|
||||||
|
Add `crates/alknet-tls` to the workspace `members` list in the root `Cargo.toml`.
|
||||||
|
|
||||||
|
### Module skeleton
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/lib.rs
|
||||||
|
//! alknet-tls: TLS setup types for alknet — server config, client config,
|
||||||
|
//! verifiers, cert resolvers, and shared signing helpers.
|
||||||
|
//!
|
||||||
|
//! Provides `TlsServerConfig` (server-side TLS setup) and `TlsClientConfig`
|
||||||
|
//! (client-side TLS setup), both transport-agnostic. Transport-specific
|
||||||
|
//! conversion (e.g. `for_quinn()`) is feature-gated.
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod pem;
|
||||||
|
pub mod server;
|
||||||
|
pub mod signing;
|
||||||
|
|
||||||
|
// Re-exports (filled in by subsequent tasks)
|
||||||
|
```
|
||||||
|
|
||||||
|
Each module file gets a doc comment and `// TODO: implement` marker.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `crates/alknet-tls/Cargo.toml` exists with all dependencies and feature flags
|
||||||
|
- [ ] `crates/alknet-tls/src/lib.rs` exists with module declarations
|
||||||
|
- [ ] Module skeleton files exist: `server.rs`, `client.rs`, `signing.rs`, `pem.rs`
|
||||||
|
- [ ] Root `Cargo.toml` `members` list includes `crates/alknet-tls`
|
||||||
|
- [ ] `cargo check -p alknet-tls` succeeds
|
||||||
|
- [ ] `cargo clippy -p alknet-tls` succeeds with no warnings
|
||||||
|
- [ ] Dual licensing: `MIT OR Apache-2.0` (workspace-inherited)
|
||||||
|
- [ ] `alknet-core` dependency uses workspace path (`path = "../alknet-core"`)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/alknet-crate-extraction/findings.md — Phase 1
|
||||||
|
- docs/architecture/decisions/088-webpki-roots-fallback.md — ADR-088 §5
|
||||||
|
- crates/alknet-core/Cargo.toml — reference for dep versions
|
||||||
|
- crates/alknet-call/Cargo.toml — reference for dep versions
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is the foundational setup task for alknet-tls. All subsequent tls/*
|
||||||
|
> tasks depend on this one. The crate has no alknet dependencies beyond core.
|
||||||
|
> `rustls-native-certs` and `webpki-roots` are always-present (not feature-gated)
|
||||||
|
> per ADR-088 §5. The `quinn`/`tcp`/`acme` features gate transport-specific
|
||||||
|
> conversion methods, not the core TLS types.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
id: tls/review-tls
|
||||||
|
name: Review alknet-tls implementation for spec conformance, deduplication, and test coverage
|
||||||
|
status: pending
|
||||||
|
depends_on: [tls/tests]
|
||||||
|
scope: moderate
|
||||||
|
risk: low
|
||||||
|
impact: phase
|
||||||
|
level: review
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 1 review checkpoint. Verify the `alknet-tls` crate is spec-conformant,
|
||||||
|
self-contained, and ready for downstream consumption by `alknet-endpoint` (Phase 2)
|
||||||
|
and `alknet-client` (Phase 3).
|
||||||
|
|
||||||
|
### Review Checklist
|
||||||
|
|
||||||
|
1. **Crate structure**:
|
||||||
|
- Module layout matches spec: `server.rs`, `client.rs`, `signing.rs`, `pem.rs`
|
||||||
|
- Public API types: `TlsServerConfig`, `TlsClientConfig`, `TlsError`, `Ed25519SigningKey`
|
||||||
|
- Re-exports in `lib.rs` are correct and minimal
|
||||||
|
|
||||||
|
2. **Server-side conformance**:
|
||||||
|
- `TlsServerConfig::new()` accepts `&TlsIdentity` + `&[Vec<u8>]` and returns `Result<Self, TlsError>`
|
||||||
|
- `TlsServerConfig::for_quinn()` converts to `quinn::ServerConfig` (feature-gated)
|
||||||
|
- `RawKeyCertResolver` implements `ResolvesServerCert` with `only_raw_public_keys() == true`
|
||||||
|
- `AcceptAnyCertVerifier` implements `ClientCertVerifier` in "request-but-don't-require" mode
|
||||||
|
- `SelfSignedCert` generation uses `rcgen`
|
||||||
|
- ACME path (`TlsSetup::new_acme`) is feature-gated on `acme`
|
||||||
|
- `build_iroh_endpoint` is either extracted (feature-gated on `iroh`) or deferred with a TODO
|
||||||
|
|
||||||
|
3. **Client-side conformance**:
|
||||||
|
- `TlsClientConfig::new()` accepts `&ConnectionCredentials` + `&[u8]` and returns `Result<Self, TlsError>`
|
||||||
|
- `TlsClientConfig::for_quinn()` converts to `quinn::ClientConfig` (feature-gated)
|
||||||
|
- `FingerprintPinVerifier` implements `ServerCertVerifier` with fingerprint matching
|
||||||
|
- `select_server_verifier` logic: `Some` → fingerprint pin, `None` → CA verification (ADR-034 §3)
|
||||||
|
- `RawKeyClientCertResolver` implements `ResolvesClientCert` with `only_raw_public_keys()` detection
|
||||||
|
- `NoClientCertResolver` implements `ResolvesClientCert` with `has_certs() == false`
|
||||||
|
- `load_platform_root_cert_store` includes `webpki-roots` fallback (ADR-088 §5)
|
||||||
|
|
||||||
|
4. **Shared code deduplication**:
|
||||||
|
- `Ed25519SigningKey` is defined once in `signing.rs`, used by both server and client
|
||||||
|
- `load_cert_chain` / `load_private_key` are defined once in `pem.rs`, used by both
|
||||||
|
- No duplicate `Ed25519SigningKey` or PEM loaders between server and client modules
|
||||||
|
|
||||||
|
5. **Dependency hygiene**:
|
||||||
|
- `rustls-native-certs` and `webpki-roots` are always-present (not feature-gated) per ADR-088 §5
|
||||||
|
- `quinn` is optional, gated behind `quinn` feature
|
||||||
|
- `tokio-rustls` is optional, gated behind `tcp` feature
|
||||||
|
- `rustls-acme` is optional, gated behind `acme` feature
|
||||||
|
- No unexpected heavy deps
|
||||||
|
|
||||||
|
6. **Error handling**:
|
||||||
|
- `TlsError` has `Config`, `Io`, `Cert` variants
|
||||||
|
- All public fallible functions return `Result<_, TlsError>` (no raw `String` errors)
|
||||||
|
- Error messages are descriptive
|
||||||
|
|
||||||
|
7. **Test coverage**:
|
||||||
|
- All 22 server-side tests pass
|
||||||
|
- All 10 client-side tests pass
|
||||||
|
- `Ed25519SigningKey` tests (6) pass
|
||||||
|
- PEM loader tests (3) pass
|
||||||
|
- Tests exercise error paths (missing files, wrong fingerprints, etc.)
|
||||||
|
- Feature-gated tests are correctly annotated
|
||||||
|
|
||||||
|
8. **Cross-cutting checks**:
|
||||||
|
- `cargo build -p alknet-tls` succeeds (all feature combos)
|
||||||
|
- `cargo test -p alknet-tls` succeeds (all feature combos)
|
||||||
|
- `cargo clippy -p alknet-tls --all-targets` succeeds with no warnings
|
||||||
|
- `cargo fmt --check -p alknet-tls` passes
|
||||||
|
- `cargo build --workspace` still succeeds (old code untouched)
|
||||||
|
- `cargo test --workspace` still succeeds (old tests untouched)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] Crate structure matches spec (4 modules, public API types)
|
||||||
|
- [ ] `TlsServerConfig` API correct and feature-gated
|
||||||
|
- [ ] `TlsClientConfig` API correct and feature-gated
|
||||||
|
- [ ] `Ed25519SigningKey` deduplicated (one copy in `signing.rs`)
|
||||||
|
- [ ] `load_cert_chain` / `load_private_key` deduplicated (one copy in `pem.rs`)
|
||||||
|
- [ ] `webpki-roots` fallback present in `load_platform_root_cert_store`
|
||||||
|
- [ ] `rustls-native-certs` + `webpki-roots` always-present (not feature-gated)
|
||||||
|
- [ ] All 41 tests pass (22 server + 10 client + 6 signing + 3 pem)
|
||||||
|
- [ ] `cargo build -p alknet-tls` succeeds (all feature combos)
|
||||||
|
- [ ] `cargo test -p alknet-tls` succeeds (all feature combos)
|
||||||
|
- [ ] `cargo clippy -p alknet-tls --all-targets` succeeds with no warnings
|
||||||
|
- [ ] `cargo fmt --check -p alknet-tls` passes
|
||||||
|
- [ ] Workspace still green: `cargo build --workspace` + `cargo test --workspace` pass
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/alknet-crate-extraction/findings.md — Phase 1
|
||||||
|
- docs/architecture/decisions/088-webpki-roots-fallback.md — ADR-088 §5
|
||||||
|
- docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md — ADR-034 §3
|
||||||
|
- tasks/tls/crate-init.md
|
||||||
|
- tasks/tls/server-extract.md
|
||||||
|
- tasks/tls/client-extract.md
|
||||||
|
- tasks/tls/tests.md
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This review gates Phase 1 completion. The crate must be self-contained and
|
||||||
|
> spec-conformant before Phase 2 (`alknet-endpoint`) and Phase 3 (`alknet-client`)
|
||||||
|
> begin, since both depend on `alknet-tls`. The old code in core and call is
|
||||||
|
> intentionally still present (duplicated) — the prunes happen in Phases 4-5.
|
||||||
|
> If deviations are found, document and fix before proceeding to Phase 2.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
id: tls/server-extract
|
||||||
|
name: Extract server-side TLS code from alknet-core/endpoint.rs into alknet-tls
|
||||||
|
status: pending
|
||||||
|
depends_on: [tls/crate-init]
|
||||||
|
scope: moderate
|
||||||
|
risk: medium
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 1, Task 2 of the crate extraction. Extract the server-side TLS setup code from
|
||||||
|
`crates/alknet-core/src/endpoint.rs` (lines 493-934) into `crates/alknet-tls/src/server.rs`
|
||||||
|
and `crates/alknet-tls/src/signing.rs` + `crates/alknet-tls/src/pem.rs` (shared helpers).
|
||||||
|
|
||||||
|
The old code **stays** in `endpoint.rs` (duplicated) — no breakage. The new crate is
|
||||||
|
self-contained and builds standalone.
|
||||||
|
|
||||||
|
### Types to extract
|
||||||
|
|
||||||
|
From `endpoint.rs` lines 493-934:
|
||||||
|
|
||||||
|
| Type/Function | Lines | Destination |
|
||||||
|
|---------------|-------|-------------|
|
||||||
|
| `TlsSetup` struct + `new()` + `new_acme()` | 493-611 | `server.rs` |
|
||||||
|
| `build_quinn_server_config_from_rustls()` | 614-624 | `server.rs` |
|
||||||
|
| `build_rustls_server_config()` | 626-674 | `server.rs` |
|
||||||
|
| `build_iroh_endpoint()` | 676-703 | `server.rs` (feature-gated on `iroh`) |
|
||||||
|
| `load_cert_chain()` | 705-714 | `pem.rs` (shared) |
|
||||||
|
| `load_private_key()` | 716-730 | `pem.rs` (shared) |
|
||||||
|
| `SelfSignedCert` struct | 732-736 | `server.rs` |
|
||||||
|
| `generate_self_signed_cert()` | 738-755 | `server.rs` |
|
||||||
|
| `AcceptAnyCertVerifier` struct + impls | 757-834 | `server.rs` |
|
||||||
|
| `RawKeyCertResolver` struct + impls | 836-873 | `server.rs` |
|
||||||
|
| `Ed25519SigningKey` struct + impls | 875-933 | `signing.rs` (shared) |
|
||||||
|
|
||||||
|
### New public API types
|
||||||
|
|
||||||
|
The extracted code currently uses free functions and private structs. Wrap them in
|
||||||
|
public API types for the crate:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// server.rs
|
||||||
|
|
||||||
|
/// Server-side TLS configuration, transport-agnostic.
|
||||||
|
/// Wraps a `rustls::ServerConfig` plus optional ACME state.
|
||||||
|
pub struct TlsServerConfig {
|
||||||
|
pub(crate) rustls_config: rustls::ServerConfig,
|
||||||
|
#[cfg(feature = "acme")]
|
||||||
|
pub(crate) acme_state_handle: Option<tokio::task::JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsServerConfig {
|
||||||
|
/// Build a server config from a `TlsIdentity` and ALPN list.
|
||||||
|
/// ACME identities spawn a background cert-renewal task.
|
||||||
|
pub async fn new(
|
||||||
|
tls_identity: &alknet_core::config::TlsIdentity,
|
||||||
|
alpns: &[Vec<u8>],
|
||||||
|
) -> Result<Self, TlsError> { ... }
|
||||||
|
|
||||||
|
/// Convert to a `quinn::ServerConfig` for QUIC transport.
|
||||||
|
#[cfg(feature = "quinn")]
|
||||||
|
pub fn for_quinn(self) -> Result<quinn::ServerConfig, TlsError> { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// signing.rs
|
||||||
|
|
||||||
|
/// Ed25519 signing key usable as both a rustls `SigningKey` and `Signer`.
|
||||||
|
/// Consolidated — one copy used by both server (`RawKeyCertResolver`) and
|
||||||
|
/// client (`RawKeyClientCertResolver`).
|
||||||
|
pub struct Ed25519SigningKey { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// pem.rs
|
||||||
|
|
||||||
|
/// Load a PEM-encoded certificate chain from a file path.
|
||||||
|
pub fn load_cert_chain(path: &Path) -> Result<Vec<CertificateDer<'static>>, TlsError> { ... }
|
||||||
|
|
||||||
|
/// Load a PEM-encoded private key from a file path.
|
||||||
|
pub fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>, TlsError> { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### TlsError
|
||||||
|
|
||||||
|
Define a unified error type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum TlsError {
|
||||||
|
#[error("TLS config error: {0}")]
|
||||||
|
Config(String),
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("certificate error: {0}")]
|
||||||
|
Cert(String),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adaptations
|
||||||
|
|
||||||
|
1. **Error types**: Replace `EndpointError::TlsConfig(...)` with `TlsError::Config(...)` or `TlsError::Io(...)`. The `EndpointError` type stays in core — the extracted code uses `TlsError` instead.
|
||||||
|
2. **Imports**: Update all `crate::config::*` imports to `alknet_core::config::*`. Update `crate::fingerprint::*` to `alknet_core::fingerprint::*`.
|
||||||
|
3. **`Ed25519SigningKey`**: Move to `signing.rs` as a shared type. Both `server.rs` and (later) `client.rs` will use it from there.
|
||||||
|
4. **`load_cert_chain` / `load_private_key`**: Move to `pem.rs` as shared functions. Both `server.rs` and (later) `client.rs` will use them from there.
|
||||||
|
5. **`build_iroh_endpoint`**: This is an iroh-specific builder, not pure TLS. Gate on `#[cfg(feature = "iroh")]` and depend on `alknet-core/iroh`. If the iroh dep is too heavy for `alknet-tls`, leave it in core for now and note as a TODO.
|
||||||
|
6. **Feature gates**: `AcceptAnyCertVerifier`, `RawKeyCertResolver`, `SelfSignedCert`, `generate_self_signed_cert`, `build_rustls_server_config`, `build_quinn_server_config_from_rustls` are gated on `#[cfg(feature = "quinn")]`. `build_iroh_endpoint` is gated on `#[cfg(feature = "iroh")]`. `TlsSetup::new_acme` is gated on `#[cfg(feature = "acme")]`.
|
||||||
|
|
||||||
|
### What stays in core
|
||||||
|
|
||||||
|
The old code in `endpoint.rs` lines 493-934 is **not deleted** — it stays as a duplicate.
|
||||||
|
The prune happens in Phase 4. This task only adds code to `alknet-tls`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `crates/alknet-tls/src/server.rs` contains `TlsServerConfig`, `TlsSetup`, `build_rustls_server_config`, `build_quinn_server_config_from_rustls`, `RawKeyCertResolver`, `AcceptAnyCertVerifier`, `SelfSignedCert`, `generate_self_signed_cert`
|
||||||
|
- [ ] `crates/alknet-tls/src/signing.rs` contains `Ed25519SigningKey` with `SigningKey` + `Signer` impls
|
||||||
|
- [ ] `crates/alknet-tls/src/pem.rs` contains `load_cert_chain` + `load_private_key`
|
||||||
|
- [ ] `TlsServerConfig::new()` accepts `&TlsIdentity` + `&[Vec<u8>]` and returns `Result<Self, TlsError>`
|
||||||
|
- [ ] `TlsServerConfig::for_quinn()` converts to `quinn::ServerConfig` (feature-gated)
|
||||||
|
- [ ] `TlsError` enum has `Config`, `Io`, `Cert` variants
|
||||||
|
- [ ] All extracted code uses `TlsError` (not `EndpointError`)
|
||||||
|
- [ ] All extracted code imports from `alknet_core` (not `crate::`)
|
||||||
|
- [ ] Feature gates correct: `quinn` for TLS types, `acme` for ACME, `iroh` for iroh builder
|
||||||
|
- [ ] `cargo check -p alknet-tls` succeeds (all feature combos)
|
||||||
|
- [ ] `cargo clippy -p alknet-tls` succeeds with no warnings
|
||||||
|
- [ ] `cargo test -p alknet-core` still passes (old code untouched)
|
||||||
|
- [ ] `cargo test -p alknet-call` still passes (old code untouched)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/alknet-crate-extraction/findings.md — Phase 1, server-side extraction
|
||||||
|
- crates/alknet-core/src/endpoint.rs — lines 493-934 (source code to extract)
|
||||||
|
- crates/alknet-core/src/config.rs — `TlsIdentity`, `Ed25519SecretKey`, `AcmeDirectory`
|
||||||
|
- crates/alknet-core/src/fingerprint.rs — `extract_ed25519_raw_key_from_spki`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is the largest single extraction in Phase 1 (~440 lines). The code is
|
||||||
|
> well-understood and tested — the main work is adapting error types and imports.
|
||||||
|
> `Ed25519SigningKey` and `load_cert_chain`/`load_private_key` are extracted to
|
||||||
|
> shared modules because the client-side extraction (next task) also needs them.
|
||||||
|
> The `build_iroh_endpoint` function may be deferred if the iroh dep is too heavy
|
||||||
|
> for `alknet-tls` — note as TODO if so. The old code in `endpoint.rs` is NOT
|
||||||
|
> deleted — that's Phase 4.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
id: tls/tests
|
||||||
|
name: Move and adapt TLS tests from endpoint.rs and call_client.rs into alknet-tls
|
||||||
|
status: pending
|
||||||
|
depends_on: [tls/client-extract]
|
||||||
|
scope: moderate
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 1, Task 4 of the crate extraction. Move the TLS-related tests from
|
||||||
|
`crates/alknet-core/src/endpoint.rs` and `crates/alknet-call/src/client/call_client.rs`
|
||||||
|
into `crates/alknet-tls/`. Adapt them to test the new public API types
|
||||||
|
(`TlsServerConfig`, `TlsClientConfig`) instead of the old free functions.
|
||||||
|
|
||||||
|
The old tests **stay** in their original files (duplicated) — no breakage. The new
|
||||||
|
crate's tests are self-contained and pass standalone.
|
||||||
|
|
||||||
|
### Server-side tests to move (from `endpoint.rs`)
|
||||||
|
|
||||||
|
22 tests total. Move to `crates/alknet-tls/src/server.rs` `#[cfg(test)] mod tests`:
|
||||||
|
|
||||||
|
| Test | Line | What it tests |
|
||||||
|
|------|------|---------------|
|
||||||
|
| `raw_key_cert_resolver_only_raw_public_keys` | 1099 | `RawKeyCertResolver` trait impl |
|
||||||
|
| `self_signed_cert_generation_produces_cert_and_key` | 1204 | `generate_self_signed_cert` |
|
||||||
|
| `acme_directory_production_url` | 1255 | `AcmeDirectory::Production` URL |
|
||||||
|
| `acme_directory_staging_url` | 1262 | `AcmeDirectory::Staging` URL |
|
||||||
|
| `acme_directory_custom_url` | 1272 | `AcmeDirectory::Custom` URL |
|
||||||
|
| `tls_setup_x509_returns_no_acme_state` | 1280 | `TlsSetup::new` with X509 |
|
||||||
|
| `build_rustls_server_config_raw_key_succeeds` | 1368 | `build_rustls_server_config` RawKey |
|
||||||
|
| `build_rustls_server_config_self_signed_succeeds` | 1379 | `build_rustls_server_config` SelfSigned |
|
||||||
|
| `build_rustls_server_config_acme_is_unreachable` | 1391 | ACME guard in `build_rustls_server_config` |
|
||||||
|
| `build_quinn_server_config_from_rustls_succeeds` | 1403 | `build_quinn_server_config_from_rustls` |
|
||||||
|
| `load_private_key_returns_error_when_no_key_present` | 1415 | `load_private_key` error path |
|
||||||
|
| `load_private_key_returns_error_when_file_missing` | 1428 | `load_private_key` missing file |
|
||||||
|
| `load_cert_chain_returns_error_when_file_missing` | 1440 | `load_cert_chain` missing file |
|
||||||
|
| `accept_any_cert_verifier_offers_and_does_not_require_client_auth` | 1454 | `AcceptAnyCertVerifier` trait |
|
||||||
|
| `accept_any_cert_verifier_verifies_any_client_cert` | 1464 | `AcceptAnyCertVerifier` verify |
|
||||||
|
| `accept_any_cert_verifier_supported_schemes_are_non_empty` | 1478 | `AcceptAnyCertVerifier` schemes |
|
||||||
|
| `accept_any_cert_verifier_debug_is_implemented` | 1489 | `AcceptAnyCertVerifier` Debug |
|
||||||
|
| `ed25519_signing_key_choose_scheme_returns_some_for_ed25519` | 1499 | `Ed25519SigningKey` choose_scheme |
|
||||||
|
| `ed25519_signing_key_choose_scheme_returns_none_without_ed25519` | 1512 | `Ed25519SigningKey` no ED25519 |
|
||||||
|
| `ed25519_signing_key_algorithm_is_ed25519` | 1525 | `Ed25519SigningKey` algorithm |
|
||||||
|
| `ed25519_signing_key_public_key_returns_spki` | 1534 | `Ed25519SigningKey` public_key |
|
||||||
|
| `ed25519_signing_key_signer_signs_message` | 1545 | `Ed25519SigningKey` sign |
|
||||||
|
| `ed25519_signing_key_debug_does_not_leak_material` | 1560 | `Ed25519SigningKey` Debug |
|
||||||
|
| `raw_key_cert_resolver_debug_is_implemented` | 1569 | `RawKeyCertResolver` Debug |
|
||||||
|
|
||||||
|
### Client-side tests to move (from `call_client.rs`)
|
||||||
|
|
||||||
|
10 tests total. Move to `crates/alknet-tls/src/client.rs` `#[cfg(test)] mod tests`:
|
||||||
|
|
||||||
|
| Test | Line | What it tests | Adaptation |
|
||||||
|
|------|------|---------------|------------|
|
||||||
|
| `fingerprint_pin_verifier_matches_correct_ed25519_fingerprint` | 750 | verifier accept | Test via `FingerprintPinVerifier` directly (no change needed) |
|
||||||
|
| `fingerprint_pin_verifier_rejects_wrong_ed25519_fingerprint` | 769 | verifier reject | Same |
|
||||||
|
| `fingerprint_pin_verifier_matches_correct_sha256_fingerprint` | 789 | verifier X.509 accept | Same |
|
||||||
|
| `fingerprint_pin_verifier_rejects_wrong_sha256_fingerprint` | 806 | verifier X.509 reject | Same |
|
||||||
|
| `select_server_verifier_returns_ca_verifier_for_none` | 822 | CA path | Test via `TlsClientConfig::new` or keep as unit test of internal fn |
|
||||||
|
| `select_server_verifier_returns_fingerprint_pin_for_some` | 839 | pin path | Same |
|
||||||
|
| `build_client_auth_presents_ed25519_raw_key_without_error` | 857 | client cert resolver | Test via `TlsClientConfig::new` or keep as unit test |
|
||||||
|
| `build_client_auth_none_resolves_to_no_client_cert` | 879 | no-cert resolver | Same |
|
||||||
|
| `build_quinn_client_config_with_raw_key_identity_builds_without_error` | 893 | full config build | Adapt to test `TlsClientConfig::new` + `for_quinn()` |
|
||||||
|
| `build_quinn_client_config_with_no_remote_identity_builds_without_error` | 909 | CA-verify config | Adapt to test `TlsClientConfig::new` + `for_quinn()` |
|
||||||
|
|
||||||
|
### Test adaptations
|
||||||
|
|
||||||
|
1. **Imports**: Update to use `alknet_tls::*` types, `alknet_core::config::*`, etc.
|
||||||
|
2. **Server tests**: Most server-side tests test free functions directly — they can stay
|
||||||
|
as unit tests of the internal functions, or be adapted to test through `TlsServerConfig::new()`.
|
||||||
|
The `tls_setup_x509_returns_no_acme_state` test should go through `TlsServerConfig::new()`.
|
||||||
|
3. **Client tests**: The `build_quinn_client_config_*` tests should be adapted to test
|
||||||
|
`TlsClientConfig::new(credentials, alpn)?.for_quinn()` instead of the free function.
|
||||||
|
The verifier and client-auth tests can stay as unit tests of the internal functions.
|
||||||
|
4. **`Ed25519SigningKey` tests**: Move to `crates/alknet-tls/src/signing.rs` `#[cfg(test)] mod tests`.
|
||||||
|
5. **`load_cert_chain` / `load_private_key` tests**: Move to `crates/alknet-tls/src/pem.rs` `#[cfg(test)] mod tests`.
|
||||||
|
6. **Test helpers**: The `build_ed25519_spki_der`, `build_x509_cert_der`, `aws_lc_rs_provider`,
|
||||||
|
`verify_pin` helpers from `call_client.rs` tests should move with the tests that use them.
|
||||||
|
7. **Feature gates**: All quinn-dependent tests need `#[cfg(feature = "quinn")]`. The
|
||||||
|
`acme_directory_*` tests don't need quinn. The `ed25519_signing_key_*` tests need quinn
|
||||||
|
(they use `rustls::sign::SigningKey`).
|
||||||
|
|
||||||
|
### What stays in the original files
|
||||||
|
|
||||||
|
The old tests in `endpoint.rs` and `call_client.rs` are **not deleted** — they stay as
|
||||||
|
duplicates. The prune happens in Phase 4 (core) and Phase 5 (call). This task only adds
|
||||||
|
tests to `alknet-tls`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] All 22 server-side TLS tests moved to `alknet-tls/src/server.rs` and pass
|
||||||
|
- [ ] All 10 client-side TLS tests moved to `alknet-tls/src/client.rs` and pass
|
||||||
|
- [ ] `Ed25519SigningKey` tests (6) moved to `alknet-tls/src/signing.rs` and pass
|
||||||
|
- [ ] `load_cert_chain` / `load_private_key` tests (3) moved to `alknet-tls/src/pem.rs` and pass
|
||||||
|
- [ ] `build_quinn_client_config_*` tests adapted to use `TlsClientConfig::new().for_quinn()`
|
||||||
|
- [ ] `tls_setup_x509_returns_no_acme_state` adapted to use `TlsServerConfig::new()`
|
||||||
|
- [ ] All test helpers (`build_ed25519_spki_der`, `build_x509_cert_der`, `aws_lc_rs_provider`, `verify_pin`) moved with their tests
|
||||||
|
- [ ] Feature gates correct on all moved tests
|
||||||
|
- [ ] `cargo test -p alknet-tls` passes (all feature combos)
|
||||||
|
- [ ] `cargo test -p alknet-core` still passes (old tests untouched)
|
||||||
|
- [ ] `cargo test -p alknet-call` still passes (old tests untouched)
|
||||||
|
- [ ] `cargo clippy -p alknet-tls --all-targets` succeeds with no warnings
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/research/alknet-crate-extraction/findings.md — Phase 1, test lists
|
||||||
|
- crates/alknet-core/src/endpoint.rs — lines 935-1606 (server-side tests)
|
||||||
|
- crates/alknet-call/src/client/call_client.rs — lines 569-930 (client-side tests)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> This is the test migration task — 32 tests total (22 server + 10 client).
|
||||||
|
> The tests are well-understood and mostly need import updates. The
|
||||||
|
> `build_quinn_client_config_*` tests need the most adaptation (testing through
|
||||||
|
> `TlsClientConfig` instead of the free function). The old tests stay in their
|
||||||
|
> original files — the prune happens in Phases 4-5. Test helpers that are shared
|
||||||
|
> between multiple test functions should move to a `#[cfg(test)]` module in the
|
||||||
|
> same file.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> To be filled on completion
|
||||||
Reference in new issue
Block a user