diff options
| -rw-r--r-- | CLAUDE.md | 296 |
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 | |
