Files
alknet/tasks/core/rawkey-decouple-from-iroh.md
glm-5.2 00edfc0889 feat(core): ADR-027 — RawKey decoupling, client cert request, ACME integration
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.
2026-06-24 20:29:43 +00:00

5.4 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
core/rawkey-decouple-from-iroh Decouple TlsIdentity::RawKey from the iroh feature (ADR-027) completed
narrow medium component 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):

    #[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 Ed25519SecretKeyiroh::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.