summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CLAUDE.md296
1 files changed, 296 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..028f65b
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,296 @@
+# CLAUDE.md — fog Codebase Guide
+
+This file provides context for AI assistants working on the fog repository.
+
+---
+
+## Project Overview
+
+**fog** is a privacy-preserving anonymous SMTP relay network. It implements Sphinx mixnet routing with post-quantum cryptography (Kyber-768) to provide sender anonymity, forward secrecy, and resistance to traffic analysis. Messages are routed through 3–6 encrypted hops over Tor before being delivered.
+
+- **Version**: 4.1.0
+- **Language**: Go 1.21+
+- **License**: MIT
+- **Architecture**: Single binary, monolithic source file
+
+---
+
+## Repository Structure
+
+```
+fog/
+├── fog.go # Entire application (~2,355 lines) — single source file
+├── fog-client.sh # Interactive Bash SMTP client for end users
+├── go.mod # Go module: "fog", requires go 1.21
+├── go.sum # Dependency checksums
+├── README.md # User-facing documentation and quick start guide
+└── LICENSE # MIT License
+```
+
+There is **no Makefile, no CI/CD, no Docker configuration, and no test files** in this repository.
+
+---
+
+## Build & Run
+
+### Build
+
+```bash
+go mod tidy
+go build -tags="sqlite_omit_load_extension" -ldflags="-s -w" -trimpath -o fog fog.go
+```
+
+The `-tags="sqlite_omit_load_extension"` tag is required for the SQLite dependency. The `-ldflags="-s -w" -trimpath` flags strip debug info for smaller production binaries.
+
+### Run (development)
+
+```bash
+./fog -name test.onion -short-name test -smtp 127.0.0.1:2525 -debug
+```
+
+### Tests
+
+```bash
+go test ./...
+```
+
+Note: There are currently no test files in the repository.
+
+### Export node identity (first-time setup)
+
+```bash
+./fog -name your-onion-address.onion -short-name yourname -export-node-info
+```
+
+---
+
+## Command-Line Flags
+
+| Flag | Default | Description |
+|------|---------|-------------|
+| `-name` | `fog.onion` | Node hostname (.onion address) |
+| `-short-name` | — | Short label for logs |
+| `-smtp` | `127.0.0.1:2525` | SMTP listen address |
+| `-node` | `127.0.0.1:9999` | Sphinx node listen address |
+| `-sphinx` | `false` | Enable Sphinx mixnet routing |
+| `-pki` | — | Path to bootstrap PKI file (`nodes.json`) |
+| `-key` | — | Path to persistent node key file |
+| `-debug` | `false` | Enable verbose debug logging |
+| `-export-node-info` | — | Print node info JSON and exit |
+| `-version` | — | Print version and feature list, then exit |
+
+---
+
+## Dependencies
+
+Defined in `go.mod`:
+
+| Package | Version | Purpose |
+|---------|---------|---------|
+| `github.com/mattn/go-sqlite3` | v1.14.18 | SQLite driver for persistent message queue |
+| `golang.org/x/crypto` | v0.17.0 | HKDF key derivation |
+| `golang.org/x/net` | v0.19.0 | SOCKS5 proxy support (Tor) |
+| `github.com/symbolicsoft/kyber-k2so` | (indirect) | Kyber-768 post-quantum KEM |
+
+The `kyber-k2so` package is imported directly in `fog.go:54` as `kyberk2so` but is not listed in `go.mod` — run `go mod tidy` to resolve this if needed.
+
+---
+
+## Source Code Architecture (`fog.go`)
+
+The entire application lives in `fog.go`. It is organized into clearly delimited sections marked by banner comments:
+
+```
+// =============================================================================
+// SECTION NAME
+// =============================================================================
+```
+
+### Key Sections and Approximate Line Ranges
+
+| Section | Approx. Lines | Description |
+|---------|--------------|-------------|
+| Constants & imports | 1–100 | Protocol constants, sizing, intervals |
+| Global state | ~100–160 | Package-level vars (`pki`, `pool`, `queue`, `stats`, etc.) |
+| PKI / Node types | ~160–400 | `Node`, `PKI`, gossip, health state |
+| Sphinx cryptography | ~400–900 | Kyber-768 KEM, packet creation/processing, HMAC |
+| Batch pool | ~900–1000 | Threshold mixing (`BatchPool`, `batchWorker`) |
+| Cover traffic | ~1000–1100 | Dummy message generation (`CoverTraffic`, `coverWorker`) |
+| SMTP server | ~1100–1350 | `startSMTP`, `handleSMTP`, ESMTP protocol |
+| Node server | ~1350–1500 | `startNodeServer`, `handleNode`, Sphinx forwarding |
+| Message relay | ~1500–1750 | `relayWorker`, `processMessage`, `deliverMessage` |
+| Header sanitization | ~1750–1850 | `sanitizeHeaders` removes identifying metadata |
+| DNS via Tor | ~1850–1950 | `lookupMXViaTor`, manual DNS MX query over SOCKS5 |
+| Replay cache | ~1950–2050 | `ReplayCache`, TTL-based deduplication |
+| Health checker | ~2050–2100 | `healthChecker`, `checkNode`, `checkAllNodes` |
+| Statistics | ~2100–2120 | `statsMonitor`, periodic log output |
+| Node init | ~2120–2165 | `initNode`, key generation/loading |
+| `main()` | ~2170–2355 | Entry point, flag parsing, goroutine orchestration |
+
+### Core Types
+
+```go
+// Node represents a fog network participant
+type Node struct {
+ ID string // hex-encoded SHA-256 of public key
+ PublicKey []byte // Kyber-768 public key (1184 bytes)
+ Address string // host:port (.onion:9999)
+ Name string // human-readable short name
+ Version string
+ LastSeen time.Time
+ Healthy bool
+}
+
+// Message is the internal relay unit
+type Message struct {
+ From string
+ To []string
+ Data []byte // raw SMTP DATA content
+ IsRelay bool // true = Sphinx packet, false = original message
+ Payload []byte // encoded Sphinx packet when IsRelay=true
+}
+```
+
+### Critical Constants (`fog.go:59–99`)
+
+```go
+KyberPKSize = 1184 // Kyber-768 public key bytes
+KyberCTSize = 1088 // Kyber-768 ciphertext bytes
+HeaderSize = 1232 // Sphinx header: CT(1088) + routing(128) + MAC(16)
+PayloadMax = 64*1024 // Fixed 64 KB packet size (prevents size correlation)
+MinHops = 3
+MaxHops = 6
+BatchThresholdMin = 5
+BatchThresholdMax = 15
+```
+
+### Goroutine Map
+
+The `main()` function starts the following goroutines (when `-sphinx` is enabled):
+
+| Goroutine | Function | Purpose |
+|-----------|----------|---------|
+| Node server | `startNodeServer` | Accepts Sphinx packets from peers |
+| Health checker | `healthChecker` | Polls nodes every 3 minutes |
+| Batch worker | `batchWorker` | Flushes pool when threshold reached |
+| Gossip worker | `gossipWorker` | Exchanges PKI state every 5 minutes |
+| Cover worker | `coverWorker` | Sends dummy traffic at random intervals |
+| Relay workers (×3) | `relayWorker` | Processes message queue |
+| Stats monitor | `statsMonitor` | Logs metrics every 60 seconds |
+| Cache cleanup | `cacheCleanupWorker` | Evicts expired replay-cache entries |
+| PKI state saver | (inline) | Persists gossip state every 10 minutes |
+
+---
+
+## Runtime Files
+
+| File | Description |
+|------|-------------|
+| `nodes.json` | Bootstrap PKI — hand-crafted, **never overwritten** by fog |
+| `nodes_state.json` | Dynamic gossip state, auto-generated alongside `nodes.json` |
+| `<data-dir>/messages.db` | SQLite message queue, survives restarts |
+
+---
+
+## Protocol & Data Flow
+
+```
+Client
+ │ SMTP (port 2525)
+ ▼
+Entry node
+ │ Sphinx packet (Kyber-768, 3–6 hops, over Tor SOCKS5)
+ ▼
+Middle node(s)
+ │ Each hop decrypts one layer, adds random delay, batches with others
+ ▼
+Exit node
+ │ Final decryption → sanitize headers → MX lookup via Tor → deliver
+ ▼
+Destination SMTP server
+```
+
+**Key invariants:**
+- All inter-node traffic goes through Tor (SOCKS5 at `127.0.0.1:9050`)
+- All packets are padded to exactly 64 KB
+- DNS MX lookups are performed manually over Tor (no system resolver used)
+- The SMTP envelope (`MAIL FROM`, `RCPT TO`) is embedded in the Sphinx payload, not in headers
+
+---
+
+## Coding Conventions
+
+- **Single-file architecture**: All code lives in `fog.go`. Do not create new `.go` files unless strictly necessary.
+- **Package**: `package main` — this is a standalone binary, not a library.
+- **Error handling**: Errors are logged with structured prefixes like `[SMTP]`, `[PKI]`, `[SPHINX]`, `[HEALTH]`, `[POOL]`, `[STATS]`, `[FOG]`. Use the same prefix conventions when adding log lines.
+- **Concurrency**: Goroutines communicate via the `queue` channel (`chan *Message`, capacity 500). Shared state uses `sync.Mutex` or `sync/atomic`. Context cancellation (`ctx`) signals graceful shutdown.
+- **No external frameworks**: Standard library + the four listed dependencies only. No HTTP frameworks, CLI libraries, or ORMs.
+- **Security-sensitive code**: Cryptographic operations use constant-time comparisons (`hmac.Equal`) and `crypto/rand` exclusively. Never use `math/rand` for security purposes.
+
+---
+
+## Log Prefixes Reference
+
+| Prefix | Component |
+|--------|-----------|
+| `[FOG]` | Main / startup |
+| `[SMTP]` | SMTP server |
+| `[NODE]` | Sphinx node server |
+| `[PKI]` | PKI / gossip |
+| `[SPHINX]` | Packet creation/processing |
+| `[HEALTH]` | Node health checker |
+| `[POOL]` | Batch pool |
+| `[COVER]` | Cover traffic |
+| `[RELAY]` | Message relay worker |
+| `[DELIVER]` | Final delivery |
+| `[SANITIZE]` | Header sanitization |
+| `[DNS]` | Tor DNS lookup |
+| `[STATS]` | Statistics |
+| `[CACHE]` | Replay cache |
+| `[TOR]` | Tor SOCKS5 dialer |
+
+---
+
+## Security Constraints
+
+When modifying this codebase, preserve these invariants:
+
+1. **No DNS leaks**: MX lookups must go through `lookupMXViaTor`, never `net.LookupMX`.
+2. **No plaintext logs**: Never log message content, sender/recipient addresses in production paths.
+3. **Fixed packet size**: Sphinx payloads must remain padded to `PayloadMax` (64 KB).
+4. **Constant-time MAC verification**: Use `hmac.Equal`, not `==`, for HMAC comparisons.
+5. **Ephemeral keys only**: Each Sphinx packet uses freshly generated Kyber keypairs — never reuse keys.
+6. **Header sanitization**: The exit node must always call `sanitizeHeaders` before final delivery.
+
+---
+
+## Monitoring the Queue (SQLite)
+
+```bash
+# Total queued messages
+sqlite3 /var/lib/fog/data/messages.db \
+ "SELECT COUNT(*) FROM message_queue;"
+
+# Messages ready to send
+sqlite3 /var/lib/fog/data/messages.db \
+ "SELECT COUNT(*) FROM message_queue WHERE send_after <= strftime('%s','now');"
+```
+
+---
+
+## Stats Log Format
+
+```
+[STATS] Up:2h30m R:45 S:42 F:3 | Sphinx:40 Direct:2 | Mix R:120 F:115 | Q:23 D:156 | Healthy:4
+```
+
+| Field | Meaning |
+|-------|---------|
+| `R` | Messages received |
+| `S` | Messages sent successfully |
+| `F` | Failed deliveries |
+| `Sphinx` / `Direct` | Routing method used |
+| `Mix R` / `Mix F` | Sphinx packets received / forwarded |
+| `Q` | Currently queued in delay pool |
+| `D` | Total delayed messages delivered |
+| `Healthy` | Healthy nodes in PKI |