1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
|
# 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 = "<NYM_ADDRESS>"
journalist_pubkey = "<X25519_PUBLIC_KEY_HEX>"
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`
|