# NymDrop — Architecture v2 ## Principio fondante **La protezione è nel design, non nell'obbligo di installare qualcosa. Chi vuole anonimato aggiuntivo usa quello che trova.** La fonte usa un browser qualsiasi. Il documento viaggia interamente su Nym. Il server è un relay cieco — non conserva nulla, non sa cosa ha trasmesso. --- ## Flusso completo ``` [FONTE] [SERVER NYMDROP] [GIORNALISTA] │ │ │ │ HTTPS (browser normale) │ │ ├──────────────────────────────▶│ │ │ │ │ │ WebCrypto nel browser: │ │ cifra con chiave pubblica │ │ X25519 del giornalista │ │ │ │ │ POST ciphertext │ │ ├──────────────────────────────▶│ │ │ │ │ │ riceve ciphertext │ │ NON lo salva su disco │ │ │ │ │ nym-client embedded: │ │ invia ciphertext al │ │ Nym address del giornalista │ │ │ │ │ │ Nym mixnet (multi-hop) │ │ │ cover traffic + pad │ │ ├──────────────────────────▶│ │ │ │ │ cancella ciphertext │ │ dalla RAM │ │ │ ciphertext arriva │ │ al Nym address │ │ del giornalista │ │ │ │ │ giornalista decifra │ │ localmente con │ │ chiave privata X25519 │ │ │ │ conferma generica "ok" │ │ │◀──────────────────────────────│ │ ``` --- ## Componenti ### 1. Frontend (browser della fonte) - HTML + CSS puro, zero framework JS, zero CDN, zero cookie - **WebCrypto API** nativa del browser — nessuna libreria esterna: - Chiave pubblica X25519 del giornalista embedded nel HTML al momento del deploy - Genera keypair effimera X25519 per la sessione - ECDH key exchange → shared secret - Deriva chiave ChaCha20-Poly1305 via HKDF - Cifra testo + file nel browser - Manda solo ciphertext + public key effimera al server - Il plaintext non lascia mai il browser in chiaro - Form: textarea testo libero + upload file opzionale + submit - Funziona su qualsiasi browser moderno ### 2. Backend Go — relay cieco ``` nymdrop/ ├── cmd/ │ └── nymdrop/ │ └── main.go ← entrypoint ├── internal/ │ ├── handler/ │ │ ├── submit.go ← POST /submit │ │ └── static.go ← serve HTML/CSS/JS │ ├── relay/ │ │ └── relay.go ← ricevi → invia su Nym → cancella │ ├── nym/ │ │ └── client.go ← wrapper nym client embedded │ └── nolog/ │ └── middleware.go ← strip IP, UA, headers — zero log ├── static/ │ ├── index.html ← form submission │ └── crypto.js ← WebCrypto lato client ├── go.mod └── CLAUDE.md ``` **Comportamento del relay:** ``` ricevi ciphertext da HTTP POST ↓ passa a nym/client.go → Send(journalistNymAddress, ciphertext) ↓ Nym mixnet consegna al giornalista ↓ cancella ciphertext dalla memoria (zeroing) ↓ rispondi HTTP 200 "ok" ``` Zero scritture su disco. Zero database. Zero log. ### 3. Nym client embedded lato server Il server incorpora `nym-socks5-client` come subprocess gestito: ```go // nym/client.go type NymClient struct { addr string // Nym address del giornalista process *exec.Cmd socks5 string // localhost:1080 } func (c *NymClient) Send(payload []byte) error { // connessione SOCKS5 → Nym mixnet → giornalista conn, _ := proxy.SOCKS5("tcp", c.socks5, nil, proxy.Direct) // ... } ``` Evoluzione futura: FFI bindings Rust/Go via `sdk/ffi/shared/` — client in-process, zero subprocess. ### 4. Lato giornalista Il giornalista ha un **Nym address** dedicato a NymDrop. Riceve i documenti cifrati direttamente sul suo client Nym (NymConnect o nym-socks5-client locale). Decifra localmente con la sua chiave privata X25519 — il server non è mai coinvolto nella decifrazione. Nessuna interfaccia admin sul server — il server non ha nulla da mostrare perché non conserva nulla. --- ## No-log by design ```go func NoLog(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { r.Header.Del("X-Forwarded-For") r.Header.Del("X-Real-IP") r.Header.Del("User-Agent") r.RemoteAddr = "0.0.0.0:0" next.ServeHTTP(w, r) }) } ``` - Zero access log - Zero error log con IP - Zero timestamp salvati - Rate limiting via token bucket anonimo (contatore in RAM, reset periodico) --- ## Threat model | Minaccia | Protetto? | Come | |---|---|---| | Server legge documento | ✅ | Cifrato WebCrypto lato client — server vede solo ciphertext | | Server logga IP fonte | ✅ | No-log middleware, zero access log | | Sequestro server | ✅ | Zero documenti su disco — relay puro, inbox sempre vuota | | Traffic analysis fonte→server | ⚠️ | HTTPS normale — fonte usa quello che trova | | Traffic analysis server→giornalista | ✅ | Nym mixnet multi-hop, cover traffic, padding | | Correlazione timing | ✅ | Nym Loopix delays randomizzati | | Intercettazione documento in transito | ✅ | ChaCha20-Poly1305, solo destinatario decifra | | Compromissione chiave privata giornalista | ❌ | Fuori scope — responsabilità del giornalista | **Punto chiave per il grant:** sequestro del server = zero documenti recuperabili. Il server è matematicamente incapace di rivelare il contenuto delle submission — non perché lo voglia, ma perché non lo ha mai avuto. --- ## Deploy single container ```bash # Proxmox — CT125 pct create 125 /var/lib/vz/template/cache/debian-12.tar.zst \ --hostname nymdrop --memory 512 --cores 1 # Inside container apt install -y golang-go git clone https://github.com/gabrix73/nymdrop cd nymdrop go build ./cmd/nymdrop # Config cat > /etc/nymdrop/config.toml << EOF listen = ":443" journalist_nym_address = "" journalist_pubkey = "" tls_cert = "/etc/ssl/nymdrop/cert.pem" tls_key = "/etc/ssl/nymdrop/key.pem" EOF ./nymdrop --config /etc/nymdrop/config.toml ``` --- ## Struttura repo ``` gabrix73/nymdrop ├── cmd/nymdrop/ ├── internal/ ├── static/ ├── docs/ │ ├── ARCHITECTURE.md │ ├── THREAT_MODEL.md │ └── DEPLOY.md ├── CLAUDE.md ├── LICENSE ← AGPL-3.0 ├── README.md └── go.mod ``` --- ## Pitch grant (sintesi) NymDrop è il primo sistema di whistleblowing dove: - La fonte usa un browser qualsiasi — zero installazioni - Il documento viaggia end-to-end su Nym mixnet - Il server è un relay cieco — zero storage, zero metadati - Sequestro del server = zero documenti recuperabili - Resistente a traffic analysis globale (Loopix delays, cover traffic) Nessun sistema esistente combina questi quattro punti simultaneamente. --- ## Prossimo passo Scaffolding Go: `main.go` + `nolog/middleware.go` + `handler/submit.go` + `static/index.html` + `static/crypto.js`