Three tasks implementing ADR-027: 1. core/rawkey-decouple-from-iroh: TlsIdentity::RawKey now uses Ed25519SecretKey (alknet-core-owned wrapper over ed25519_dalek) instead of iroh::SecretKey. RawKeyCertResolver and Ed25519SigningKey un-gated from #[cfg(all(quinn, iroh))] to #[cfg(quinn)] only. Quinn-only builds (default) now support RFC 7250 raw-key identity. iroh transport converts via iroh::SecretKey::from_bytes. 2. core/endpoint-request-client-cert: replaced with_no_client_auth() with AcceptAnyCertVerifier — a custom ClientCertVerifier that requests client certs but doesn't require them or verify against a CA. alknet's identity model is fingerprint-based (the authorized_fingerprints set is the trust anchor), not PKI-based. Peer certs are extracted at the TLS layer for fingerprinting; peers without certs connect normally. 3. core/acme-integration: TlsIdentity::Acme variant (domains, cache_dir, directory, contact) + AcmeDirectory enum. TlsSetup two-phase construction: synchronous for X509/RawKey/SelfSigned, async for Acme (spawns AcmeState event loop, builds ServerConfig with ResolvesServerCertAcme). acme-tls/1 ALPN added when ACME is active; dispatch_quinn guard closes challenge connections gracefully (challenge is TLS-layer-handled). acme feature gate keeps rustls-acme out of non-ACME builds. Workspace: build/test/clippy green across all 3 feature configs (quinn-only, quinn+iroh, quinn+acme, all-features). 331 tests, 0 failures, 0 warnings.
119 lines
5.4 KiB
Markdown
119 lines
5.4 KiB
Markdown
---
|
|
id: core/rawkey-decouple-from-iroh
|
|
name: Decouple TlsIdentity::RawKey from the iroh feature (ADR-027)
|
|
status: completed
|
|
depends_on: []
|
|
scope: narrow
|
|
risk: medium
|
|
impact: component
|
|
level: implementation
|
|
---
|
|
|
|
## Description
|
|
|
|
`TlsIdentity::RawKey(iroh::SecretKey)` is gated `#[cfg(feature = "iroh")]`
|
|
and the `RawKeyCertResolver` / `Ed25519SigningKey` rustls impls are gated
|
|
`#[cfg(all(feature = "quinn", feature = "iroh"))]`. This means quinn-only
|
|
builds (the default feature set) cannot use RFC 7250 raw-key identity —
|
|
the mode described as "default for most alknet nodes" (OQ-12, ADR-027).
|
|
|
|
The coupling is artificial: `iroh::SecretKey` is a thin newtype over
|
|
`ed25519_dalek::SigningKey`. The alknet code uses only `.public().as_bytes()`,
|
|
`.sign(msg)`, and `.clone()`. This task replaces `iroh::SecretKey` with an
|
|
alknet-core-owned `Ed25519SecretKey` wrapper, un-gates the raw-key TLS
|
|
path from the `iroh` feature, and updates the iroh transport to convert.
|
|
|
|
See ADR-027 for the full design rationale.
|
|
|
|
### Implementation steps
|
|
|
|
1. **Add `ed25519-dalek` as a direct dependency** of alknet-core in
|
|
`Cargo.toml`. It's already in the lockfile (transitive via iroh).
|
|
Version: `2.2` (match what's in `Cargo.lock`).
|
|
|
|
2. **Introduce `Ed25519SecretKey`** in `config.rs` (or a new
|
|
`tls.rs` module if config.rs is getting large):
|
|
|
|
```rust
|
|
#[derive(Clone)]
|
|
pub struct Ed25519SecretKey(ed25519_dalek::SigningKey);
|
|
|
|
impl Ed25519SecretKey {
|
|
pub fn generate() -> Self { ... }
|
|
pub fn from_bytes(bytes: &[u8; 32]) -> Self { ... }
|
|
pub fn as_bytes(&self) -> &[u8; 32] { ... }
|
|
pub fn public(&self) -> ed25519_dalek::VerifyingKey { ... }
|
|
}
|
|
```
|
|
|
|
Add `ZeroizeOnDrop` (the key is secret material). Add a redacting
|
|
`Debug` impl (like `Secret<T>` in types.rs). Do NOT derive `Debug` —
|
|
the raw key bytes must not be printed.
|
|
|
|
3. **Change `TlsIdentity::RawKey`** from `RawKey(iroh::SecretKey)` to
|
|
`RawKey(Ed25519SecretKey)`. Remove the `#[cfg(feature = "iroh")]` gate
|
|
— `RawKey` is available in all builds.
|
|
|
|
4. **Rewire `Ed25519SigningKey`** in `endpoint.rs`:
|
|
- Change the inner field from `iroh::SecretKey` to `Ed25519SecretKey`
|
|
(or `ed25519_dalek::SigningKey`).
|
|
- `spki_public_key()`: use `self.key.public().as_bytes()` (same logic,
|
|
different key type — `ed25519_dalek::VerifyingKey` has `as_bytes()`).
|
|
- `sign()`: use `self.key.sign(message)` → ed25519-dalek's
|
|
`SigningKey::sign` returns `Signature` which has `to_bytes()`.
|
|
- Change the cfg gate from `#[cfg(all(feature = "quinn", feature =
|
|
"iroh"))]` to `#[cfg(feature = "quinn")]` on `RawKeyCertResolver`,
|
|
`Ed25519SigningKey`, and all related impls.
|
|
|
|
5. **Update `build_iroh_endpoint`**: when `TlsIdentity::RawKey(key)` is
|
|
present, convert to `iroh::SecretKey::from_bytes(key.as_bytes())`
|
|
before passing to `iroh::Endpoint::builder().secret_key(...)`. This
|
|
conversion is `#[cfg(feature = "iroh")]` only.
|
|
|
|
6. **Update `build_rustls_server_config`**: the `RawKey` arm changes
|
|
from `#[cfg(feature = "iroh")]` to always-available (within the
|
|
`#[cfg(feature = "quinn")]` function). The `RawKeyCertResolver::new`
|
|
takes `&Ed25519SecretKey` instead of `&iroh::SecretKey`.
|
|
|
|
7. **Update all tests** that construct `TlsIdentity::RawKey`:
|
|
- `endpoint.rs` tests: `iroh::SecretKey::generate(&mut csprng)` →
|
|
`Ed25519SecretKey::generate()`.
|
|
- Any test in `config.rs` that constructs `RawKey`.
|
|
|
|
### What NOT to change
|
|
|
|
- `TlsIdentity::X509` and `SelfSigned` — untouched by this task.
|
|
- The `endpoint-request-client-cert` task (server config client auth) —
|
|
independent, can proceed in parallel or before/after this task.
|
|
- ACME — separate follow-up task (`core/acme-integration`).
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `ed25519-dalek` is a direct dependency of alknet-core
|
|
- [ ] `Ed25519SecretKey` type exists with `generate`, `from_bytes`, `as_bytes`, `public`; redacting `Debug`; `ZeroizeOnDrop`
|
|
- [ ] `TlsIdentity::RawKey` uses `Ed25519SecretKey`, not `iroh::SecretKey`
|
|
- [ ] `TlsIdentity::RawKey` is not gated behind `#[cfg(feature = "iroh")]`
|
|
- [ ] `RawKeyCertResolver` and `Ed25519SigningKey` are gated `#[cfg(feature = "quinn")]` only (not `all(feature = "quinn", feature = "iroh")`)
|
|
- [ ] `build_iroh_endpoint` converts `Ed25519SecretKey` → `iroh::SecretKey::from_bytes`
|
|
- [ ] `cargo build -p alknet-core --features quinn` (no iroh) succeeds with `TlsIdentity::RawKey` usable
|
|
- [ ] `cargo build -p alknet-core --all-features` succeeds
|
|
- [ ] `cargo test -p alknet-core --all-features` succeeds
|
|
- [ ] `cargo test -p alknet-core --features quinn` succeeds (quinn-only, no iroh)
|
|
- [ ] `cargo clippy -p alknet-core --all-features --all-targets` clean
|
|
- [ ] `cargo clippy -p alknet-core --features quinn --all-targets` clean
|
|
|
|
## References
|
|
|
|
- ADR-027 — full design rationale
|
|
- crates/alknet-core/src/config.rs:33-41 — `TlsIdentity` enum
|
|
- crates/alknet-core/src/endpoint.rs:593-689 — `RawKeyCertResolver`, `Ed25519SigningKey`
|
|
- crates/alknet-core/src/endpoint.rs:511-538 — `build_iroh_endpoint` (conversion site)
|
|
- crates/alknet-core/src/endpoint.rs:484-495 — `build_rustls_server_config` RawKey arm
|
|
- /workspace/iroh/iroh-base/src/key.rs:261 — `iroh::SecretKey(SigningKey)` newtype
|
|
|
|
## Notes
|
|
|
|
> This is the foundation task for ADR-027. The ACME task
|
|
> (`core/acme-integration`) depends on this one because both modify
|
|
> `TlsIdentity` and `build_rustls_server_config`. Doing decoupling first
|
|
> means the ACME task builds on the cleaned-up enum without iroh coupling. |