Files
alknet/tasks/tls/client-extract.md
T
deepseek-v4-pro 4ced71f44a 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.
2026-07-17 09:29:20 +00:00

5.9 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
tls/client-extract Extract client-side TLS code from alknet-call/call_client.rs into alknet-tls pending
tls/server-extract
narrow medium component 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:

// 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