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
CloneforKeyPair - Maintains
Zeroizingsecurity for secret bytes - Enables sharing keypair across multiple actors
CLI Commands
Network Operations
- File:
bins/icnctl/src/main.rs icnctl network peers- List discovered peersicnctl network dial <did> [--addr]- Connect via QUICicnctl network stats- Show connection statisticsicnctl 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
- Automatic cleanup: Passphrase is zeroed when
Zeroizing<Vec<u8>>goes out of scope - Memory safety: Prevents passphrase recovery from memory dumps or swap
- Consistent with codebase: Matches security posture of
KeyPairimplementation - No API changes:
Zeroizing<Vec<u8>>derefs to&[u8], sokeystore.unlock(&passphrase)works unchanged
Compatibility
The fix is transparent to callers because:
Zeroizing<Vec<u8>>implementsDerefto[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:
- Prompt on startup (chosen) - secure, but requires interactive terminal
- Deferred unlock via API - better UX, delayed functionality
- 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:
- Create identity:
icnctl id init - Start daemon:
icnd(prompts for passphrase) - Verify network actor spawned: Check logs for "Network actor spawned on 0.0.0.0:4433"
- Two-node connectivity testing: Pending RPC implementation
Files Changed
New Files
crates/icn-net/src/tls.rs- DID-based TLS certificate generationcrates/icn-net/src/actor.rs- Network actor coordination
Modified Files
bins/icnd/src/main.rs- Passphrase unlock + zeroization fixbins/icnd/Cargo.toml- Added zeroize dependencycrates/icn-core/src/runtime.rs- Accept optional KeyPaircrates/icn-core/src/supervisor.rs- Spawn NetworkActor conditionallycrates/icn-identity/src/lib.rs- Implement Clone for KeyPairbins/icnctl/src/main.rs- Add network commands
Updated Files (from stubs)
crates/icn-net/src/session.rs- Full QUIC implementationcrates/icn-net/src/discovery.rs- Full mDNS implementation
Commits
3395776- Phase 2: Implement QUIC transport with TLS446213a- Phase 2: Implement mDNS discovery and network actor9408337- Integrate NetworkActor into daemon supervisor6cc8036- Add network commands to icnctl CLIf5e8bf2- Implement passphrase-based keystore unlock on daemon startup[next]- Security: Implement passphrase zeroization
Next Steps
Immediate (Phase 2 completion)
- ✅ Network layer implementation
- ✅ Supervisor integration
- ✅ CLI commands
- ✅ Passphrase unlock
- ✅ Security hardening (zeroization)
Phase 3 Preparation
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
Certificate Verification - Integrate trust graph
- Extract DID from peer certificates
- Query trust graph for peer's trust score
- Accept/reject based on trust class
Two-Node Testing - Validate end-to-end connectivity
- mDNS discovery between nodes
- QUIC handshake with DID verification
- Bidirectional stream communication
Non-Interactive Mode - Production deployment support
- Environment variable for passphrase (
ICN_KEYSTORE_PASSPHRASE) - Unix socket-based authentication
- Systemd service hardening
- Environment variable for passphrase (
Lessons Learned
Security Review Process
The passphrase zeroization issue demonstrates the importance of security review during development. Best practices:
- Audit sensitive data flows - Track credentials, keys, passphrases through entire lifecycle
- Consistent security posture - If one component uses zeroization (KeyPair), all should
- Memory safety patterns - Use
Zeroizing<T>for any sensitive data that leaves secure storage - Documentation matters - Explicit security comments help reviewers spot issues
Architecture Patterns
- Actor-based concurrency works well for network coordination
- Message-passing APIs provide clean separation of concerns
- Graceful degradation improves user experience (daemon works without actors)
- Clear error messages guide users to solutions
Testing Challenges
- Interactive components (passphrase prompts) are hard to test in CI
- Network tests require real interfaces, not suitable for unit tests
- Integration tests with two daemons require complex setup
- 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.