Phase 2: Network Layer & Security Hardening

Date: 2025-11-11 Phase: Network Transport & Discovery Status: Complete

Overview

Completed Phase 2 network layer implementation with full mDNS discovery, QUIC transport, and DID-based TLS authentication. Additionally implemented critical security hardening for passphrase handling using zeroization to prevent sensitive data leakage.

Implementation Summary

Network Transport (QUIC + TLS)

TLS Certificate Generation

  • File: crates/icn-net/src/tls.rs
  • Implemented DID-based self-signed certificate generation
  • Separate Ed25519 key for TLS (security isolation from DID signing key)
  • Custom certificate verifier (stub for future trust graph integration)
  • ALPN protocol: icn/1

Key security decision: TLS key ≠ DID signing key Rationale: Compromise of TLS key doesn't expose DID identity, allows independent key rotation

QUIC Session Management

  • File: crates/icn-net/src/session.rs
  • Quinn-based QUIC implementation
  • Connection pooling by DID
  • Multiplexed bidirectional streams (100 concurrent)
  • Graceful connection lifecycle management

mDNS Discovery

  • File: crates/icn-net/src/discovery.rs
  • Service type: _icn._udp.local
  • Broadcasts DID + version + socket address
  • Automatic peer discovery on local networks
  • Background service with clean shutdown

Network Actor Coordination

  • File: crates/icn-net/src/actor.rs
  • Unified actor managing discovery + session lifecycle
  • Message-passing API: GetPeers, Dial, GetStats
  • Tokio-based with graceful shutdown via broadcast channel

Daemon Integration

Passphrase-Based Keystore Unlock

  • File: bins/icnd/src/main.rs
  • Interactive passphrase prompt on daemon startup
  • Loads and unlocks Age-encrypted keystore
  • Passes unlocked KeyPair to Runtime → Supervisor
  • Graceful degradation: runs without actors if no keystore

Critical security fix: Implemented Zeroizing for passphrase handling (see below)

Supervisor Activation

  • File: crates/icn-core/src/supervisor.rs
  • NetworkActor spawns automatically when keypair available
  • mDNS discovery activates on daemon start
  • QUIC listener on 0.0.0.0:4433
  • Actors shut down gracefully via shutdown signal

KeyPair Clone Implementation

  • File: crates/icn-identity/src/lib.rs
  • Implemented Clone for KeyPair
  • Maintains Zeroizing security for secret bytes
  • Enables sharing keypair across multiple actors

CLI Commands

Network Operations

  • File: bins/icnctl/src/main.rs
  • icnctl network peers - List discovered peers
  • icnctl network dial <did> [--addr] - Connect via QUIC
  • icnctl network stats - Show connection statistics
  • icnctl network status - Network actor status
  • Commands are documented stubs (require RPC implementation)

Security Hardening: Passphrase Zeroization

Issue Identified

The initial implementation returned passphrases as Vec<u8> without zeroization, allowing sensitive data to persist in memory after use. This violated the principle of secure credential handling already established in the codebase (KeyPair uses Zeroizing).

Fix Implemented

Files: bins/icnd/src/main.rs, bins/icnd/Cargo.toml

// Before (insecure):
fn read_passphrase(prompt: &str) -> Result<Vec<u8>> {
    let passphrase = rpassword::read_password()?;
    Ok(passphrase.into_bytes())  // ❌ Lingers in memory
}

// After (secure):
fn read_passphrase(prompt: &str) -> Result<Zeroizing<Vec<u8>>> {
    let passphrase = rpassword::read_password()?;
    Ok(Zeroizing::new(passphrase.into_bytes()))  // ✅ Auto-zeroed on drop
}

Security Benefits

  1. Automatic cleanup: Passphrase is zeroed when Zeroizing<Vec<u8>> goes out of scope
  2. Memory safety: Prevents passphrase recovery from memory dumps or swap
  3. Consistent with codebase: Matches security posture of KeyPair implementation
  4. No API changes: Zeroizing<Vec<u8>> derefs to &[u8], so keystore.unlock(&passphrase) works unchanged

Compatibility

The fix is transparent to callers because:

  • Zeroizing<Vec<u8>> implements Deref to [u8]
  • keystore.unlock(&passphrase: &[u8]) accepts the dereferenced slice
  • No changes needed to keystore unlock interface

Architecture Decisions

1. Passphrase Unlock Strategy

Decision: Interactive prompt on daemon startup Alternatives considered:

  1. Prompt on startup (chosen) - secure, but requires interactive terminal
  2. Deferred unlock via API - better UX, delayed functionality
  3. System keyring - OS-dependent, complex integration

Rationale: Simplicity and security. Matches standard daemon patterns (SSH agent, GPG agent). Non-interactive deployments can use environment variables or socket-based auth (documented as TODO).

2. Separate TLS Key from DID

Decision: Generate fresh Ed25519 key for TLS, separate from DID signing key Rationale:

  • Blast radius containment: TLS compromise ≠ identity compromise
  • Independent rotation: TLS certs can rotate without DID changes
  • Standard PKI practice: separation of concerns

3. On-Demand Statistics Calculation

Decision: Calculate network stats on request rather than background task Rationale: Avoids borrow checker complexity with shared state, minimal performance impact for infrequent queries

Testing

Unit Tests

  • TLS certificate generation: ✅ 3/3 passing
  • QUIC session management: ✅ 1/2 passing (1 ignored for handshake verification)
  • mDNS discovery: ✅ 2/3 passing (1 ignored for network interfaces)
  • Network actor: ✅ 0/1 (1 ignored for network interfaces)

Note: Network tests require /dev/tty and network interfaces, marked as #[ignore] for CI

Integration Tests

  • Daemon starts without keystore: ✅ Shows helpful warnings
  • Daemon with passphrase prompt: ⚠️ Requires manual testing (rpassword needs /dev/tty)
  • NetworkActor spawns with identity: ✅ Logs confirm activation

Manual Testing Required

Due to interactive nature of passphrase prompt:

  1. Create identity: icnctl id init
  2. Start daemon: icnd (prompts for passphrase)
  3. Verify network actor spawned: Check logs for "Network actor spawned on 0.0.0.0:4433"
  4. Two-node connectivity testing: Pending RPC implementation

Files Changed

New Files

  • crates/icn-net/src/tls.rs - DID-based TLS certificate generation
  • crates/icn-net/src/actor.rs - Network actor coordination

Modified Files

  • bins/icnd/src/main.rs - Passphrase unlock + zeroization fix
  • bins/icnd/Cargo.toml - Added zeroize dependency
  • crates/icn-core/src/runtime.rs - Accept optional KeyPair
  • crates/icn-core/src/supervisor.rs - Spawn NetworkActor conditionally
  • crates/icn-identity/src/lib.rs - Implement Clone for KeyPair
  • bins/icnctl/src/main.rs - Add network commands

Updated Files (from stubs)

  • crates/icn-net/src/session.rs - Full QUIC implementation
  • crates/icn-net/src/discovery.rs - Full mDNS implementation

Commits

  1. 3395776 - Phase 2: Implement QUIC transport with TLS
  2. 446213a - Phase 2: Implement mDNS discovery and network actor
  3. 9408337 - Integrate NetworkActor into daemon supervisor
  4. 6cc8036 - Add network commands to icnctl CLI
  5. f5e8bf2 - Implement passphrase-based keystore unlock on daemon startup
  6. [next] - Security: Implement passphrase zeroization

Next Steps

Immediate (Phase 2 completion)

  1. ✅ Network layer implementation
  2. ✅ Supervisor integration
  3. ✅ CLI commands
  4. ✅ Passphrase unlock
  5. ✅ Security hardening (zeroization)

Phase 3 Preparation

  1. RPC Layer - Implement gRPC or JSON-RPC for daemon communication

    • Wire icnctl network commands to live daemon
    • Add authentication for daemon API access
    • Health check endpoints
  2. Certificate Verification - Integrate trust graph

    • Extract DID from peer certificates
    • Query trust graph for peer's trust score
    • Accept/reject based on trust class
  3. Two-Node Testing - Validate end-to-end connectivity

    • mDNS discovery between nodes
    • QUIC handshake with DID verification
    • Bidirectional stream communication
  4. Non-Interactive Mode - Production deployment support

    • Environment variable for passphrase (ICN_KEYSTORE_PASSPHRASE)
    • Unix socket-based authentication
    • Systemd service hardening

Lessons Learned

Security Review Process

The passphrase zeroization issue demonstrates the importance of security review during development. Best practices:

  1. Audit sensitive data flows - Track credentials, keys, passphrases through entire lifecycle
  2. Consistent security posture - If one component uses zeroization (KeyPair), all should
  3. Memory safety patterns - Use Zeroizing<T> for any sensitive data that leaves secure storage
  4. Documentation matters - Explicit security comments help reviewers spot issues

Architecture Patterns

  1. Actor-based concurrency works well for network coordination
  2. Message-passing APIs provide clean separation of concerns
  3. Graceful degradation improves user experience (daemon works without actors)
  4. Clear error messages guide users to solutions

Testing Challenges

  1. Interactive components (passphrase prompts) are hard to test in CI
  2. Network tests require real interfaces, not suitable for unit tests
  3. Integration tests with two daemons require complex setup
  4. Consider mocking strategies for future network tests

Phase Status

Phase 2: Network Transport & Discovery - ✅ COMPLETE

Deliverables:

  • ✅ mDNS discovery service
  • ✅ QUIC/TLS session management
  • ✅ Network actor coordination
  • ✅ Daemon integration with passphrase unlock
  • ✅ CLI commands for network operations
  • ✅ Security hardening (passphrase zeroization)
  • ⚠️ Certificate verification (deferred to Phase 3)
  • ⚠️ NAT traversal (deferred to Phase 2.5)

Ready for: Phase 3 (Ledger) or Phase 2.5 (RPC + Certificate Verification)


Next Journal Entry: Phase 3 Ledger implementation or Phase 2.5 RPC/Verification depending on project priorities.