Files
alknet/tasks/core/rawkey-decouple-from-iroh.md
glm-5.2 d94d7a132a docs(adr-027): TLS identity redesign — ACME + RawKey decoupling
ADR-027 resolves the architectural gap surfaced when ACME integration
became a concrete target:

1. TlsIdentity::Acme variant — static config data (domains, cache_dir,
   directory, contact) with async AcmeState constructed at endpoint
   setup via two-phase TlsSetup (not stuffed into the Clone-able enum).

2. TlsIdentity::RawKey decoupled from the iroh feature — uses
   Ed25519SecretKey (alknet-core-owned wrapper over ed25519_dalek)
   instead of iroh::SecretKey. Raw-key TLS identity (RFC 7250, the
   default for most alknet nodes) now works in quinn-only builds.
   iroh transport converts via SecretKey::from_bytes.

3. ACME feature-gated behind new acme feature (rustls-acme optional
   dep). Non-ACME builds don't compile it.

4. dispatch_quinn guard for acme-tls/1 challenge connections — TLS-ALPN-01
   is handled at the rustls cert resolver layer during the handshake;
   the guard closes challenge connections gracefully instead of logging
   a misleading "no handler" warning.

Research confirmed QUIC (quinn) handles ACME challenges differently than
TCP (reverse-proxy): quinn gives no ClientHello peek hook, but the
challenge is fully answered at the cert resolution step before the
connection surfaces to the application. No handler registration needed.

Spec updates: config.md, endpoint.md, open-questions.md (OQ-12),
overview.md + README.md (ADR index), ADR-010 (cross-ref).

Tasks: core/rawkey-decouple-from-iroh (gen 1, no deps),
core/acme-integration (gen 2, depends on rawkey). Graph: 36 tasks.
2026-06-24 12:29:24 +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) pending
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.