ciphermesh 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/README.pt-BR.md +253 -0
  4. package/bin/ciphermesh.js +34 -0
  5. package/docs/ARCHITECTURE.md +1188 -0
  6. package/docs/SETUP.md +305 -0
  7. package/docs/demo.svg +46 -0
  8. package/package.json +87 -0
  9. package/src/client/ChatController.js +2476 -0
  10. package/src/client/Connection.js +129 -0
  11. package/src/client/FileTransfer.js +488 -0
  12. package/src/client/ImagePreview.js +88 -0
  13. package/src/client/UI.js +1830 -0
  14. package/src/client/index.js +231 -0
  15. package/src/crypto/CertPinStore.js +79 -0
  16. package/src/crypto/DeniableEncrypt.js +53 -0
  17. package/src/crypto/DoubleRatchet.js +574 -0
  18. package/src/crypto/Handshake.js +219 -0
  19. package/src/crypto/HistoryStore.js +241 -0
  20. package/src/crypto/IdentityBackup.js +70 -0
  21. package/src/crypto/KeyManager.js +134 -0
  22. package/src/crypto/MessageCrypto.js +181 -0
  23. package/src/crypto/NonceManager.js +72 -0
  24. package/src/crypto/SealedSender.js +58 -0
  25. package/src/crypto/SenderKey.js +204 -0
  26. package/src/crypto/StateManager.js +138 -0
  27. package/src/crypto/TrustStore.js +216 -0
  28. package/src/p2p/Discovery.js +80 -0
  29. package/src/p2p/P2PChatController.js +1856 -0
  30. package/src/p2p/PeerConnectionManager.js +252 -0
  31. package/src/p2p/PeerServer.js +68 -0
  32. package/src/p2p/index.js +219 -0
  33. package/src/protocol/messages.js +138 -0
  34. package/src/protocol/validators.js +175 -0
  35. package/src/server/CertManager.js +173 -0
  36. package/src/server/MessageRouter.js +80 -0
  37. package/src/server/OfflineQueue.js +124 -0
  38. package/src/server/SessionManager.js +296 -0
  39. package/src/server/WebSocketServer.js +632 -0
  40. package/src/server/index.js +89 -0
  41. package/src/shared/AuditLog.js +91 -0
  42. package/src/shared/PluginManager.js +83 -0
  43. package/src/shared/banner.js +271 -0
  44. package/src/shared/commandSuggest.js +59 -0
  45. package/src/shared/config.js +90 -0
  46. package/src/shared/constants.js +126 -0
  47. package/src/shared/coverTraffic.js +34 -0
  48. package/src/shared/dnd.js +60 -0
  49. package/src/shared/emoji.js +17 -0
  50. package/src/shared/fuzzy.js +40 -0
  51. package/src/shared/invite.js +61 -0
  52. package/src/shared/keyArt.js +66 -0
  53. package/src/shared/logger.js +38 -0
  54. package/src/shared/panic.js +38 -0
  55. package/src/shared/prompt.js +31 -0
  56. package/src/shared/terminalGraphics.js +72 -0
  57. package/src/shared/themes.js +36 -0
  58. package/src/shared/voiceNote.js +128 -0
@@ -0,0 +1,1188 @@
1
+ # SecureLAN Chat — Complete Technical Documentation
2
+
3
+ > Secure chat for a local area network (LAN) with real end-to-end encryption (E2EE).
4
+ > The server **never** has access to the content of messages.
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Overview](#1-overview)
11
+ 2. [Technology Stack](#2-technology-stack)
12
+ 3. [Directory Structure](#3-directory-structure)
13
+ 4. [Responsibility of Each Module](#4-responsibility-of-each-module)
14
+ 5. [Data Model and Payloads](#5-data-model-and-payloads)
15
+ 6. [Detailed Cryptographic Flow](#6-detailed-cryptographic-flow)
16
+ 7. [Handshake Protocol](#7-handshake-protocol)
17
+ 8. [Step-by-Step Communication Flow](#8-step-by-step-communication-flow)
18
+ 9. [Initialization Strategy](#9-initialization-strategy)
19
+ 10. [Security — Threat Analysis](#10-security--threat-analysis)
20
+ 11. [Future Improvements](#11-future-improvements)
21
+ 12. [Glossary](#12-glossary)
22
+
23
+ ---
24
+
25
+ ## 1. Overview
26
+
27
+ ### What it is
28
+
29
+ SecureLAN Chat is an instant messaging system designed to operate **exclusively within a local area network (LAN)**. It uses end-to-end encryption (E2EE) based on **Curve25519 + XSalsa20-Poly1305** (via libsodium), ensuring that the server acts only as a **blind relay** — it forwards bytes it cannot read.
30
+
31
+ ### Core Principle
32
+
33
+ ```
34
+ Cliente A Servidor Cliente B
35
+ | | |
36
+ |--- payload cifrado ----->| |
37
+ | |--- payload cifrado ----->|
38
+ | | |
39
+ | O servidor NAO possui | |
40
+ | a chave para decifrar | |
41
+ ```
42
+
43
+ The server knows **only**:
44
+ - Who is connected (nickname + session ID)
45
+ - Who sent to whom (routing)
46
+ - The size of the encrypted payload
47
+ - Connection timestamps
48
+
49
+ The server **never** knows:
50
+ - The content of messages
51
+ - Private keys
52
+ - Derived shared keys
53
+
54
+ ### Topology
55
+
56
+ ```
57
+ ┌──────────────┐
58
+ │ Servidor │
59
+ │ (Relay) │
60
+ │ :3600 │
61
+ └──────┬───────┘
62
+ │ WebSocket
63
+ ┌────────────┼────────────┐
64
+ │ │ │
65
+ ┌─────┴─────┐ ┌───┴───┐ ┌─────┴─────┐
66
+ │ Cliente A │ │ ... │ │ Cliente N │
67
+ │ (Terminal) │ │ │ │ (Terminal) │
68
+ └───────────┘ └───────┘ └───────────┘
69
+ ```
70
+
71
+ This is the **star** topology (star topology) — the default mode. All clients connect to the central server. Messages are encrypted before leaving the client and decrypted only at the destination client.
72
+
73
+ ### P2P Topology (alternative mode)
74
+
75
+ ```
76
+ ┌───────────┐
77
+ │ Cliente A │
78
+ │ (Terminal) │
79
+ └─────┬─────┘
80
+ │ WebSocket direto
81
+ ┌─────────┼──────────┐
82
+ │ │
83
+ ┌───┴───┐ ┌────┴────┐
84
+ │ ... │ │ Cliente N│
85
+ │ │ │(Terminal)│
86
+ └───────┘ └─────────┘
87
+ ```
88
+
89
+ In P2P mode (`npm run p2p`), peers discover each other via mDNS on the LAN and connect directly without a central server. Same E2E encryption.
90
+
91
+ ---
92
+
93
+ ## 2. Technology Stack
94
+
95
+ ### Production Dependencies
96
+
97
+ | Package | Version | Role | Why this lib? |
98
+ |--------|--------|-------|-------------------|
99
+ | **ws** | ^8.18 | WebSocket server/client | The most mature and performant WebSocket implementation for Node.js. Zero dependencies. Natively supports binary frames (essential for encrypted payloads). Used by thousands of projects in production. |
100
+ | **sodium-native** | ^4.3 | Cryptography | Native binding of **libsodium** for Node.js. Runs in compiled C (not pure JS), offering real performance and audited security. libsodium is considered the most secure and easy-to-use modern cryptographic library. Used by Signal, Discord, Wireguard. |
101
+ | **blessed** | ^0.1.81 | Terminal UI | A library for building rich interfaces in the terminal. Supports layouts with boxes, inputs, scrolling, colors, borders — similar to ncurses but in JS. Lets you create a modern app-style UI without leaving the terminal. |
102
+ | **chalk** | ^5.4 | Terminal colors | Colored text formatting in the terminal. Used to highlight nicks, timestamps, errors, status. ESM-only in v5 (compatible with our `"type": "module"`). |
103
+ | **play-sound** | ^1.1 | Audio | Plays audio files (mp3) for sound notifications. Uses the OS's native players (mplayer, aplay, cmdmp3). |
104
+
105
+ ### Why sodium-native and not tweetnacl?
106
+
107
+ | Criterion | sodium-native | tweetnacl |
108
+ |----------|--------------|-----------|
109
+ | Implementation | C (compiled libsodium) | Pure JavaScript |
110
+ | Performance | ~100x faster | Slow |
111
+ | Audit | Formally audited | Audited but pure JS |
112
+ | Secure memory | Yes (`sodium.sodium_malloc`) | No |
113
+ | Constant-time | Guaranteed by the C code | Best-effort in JS |
114
+ | Side-channel resistance | High | Low (the JS engine may optimize) |
115
+
116
+ **sodium-native** allocates secure memory that is:
117
+ - Protected against swap (mlock)
118
+ - Zeroed when freed (sodium_memzero)
119
+ - Protected against reads by other processes
120
+
121
+ ### Development Dependencies
122
+
123
+ | Package | Role |
124
+ |--------|-------|
125
+ | **eslint** ^9.17 | Linting — ensures code quality and consistency |
126
+ | **@eslint/js** ^9.17 | ESLint's recommended base configuration |
127
+ | **globals** ^15.14 | Globals definitions (node, browser) for ESLint |
128
+ | **prettier** ^3.4 | Automatic formatting — consistent code with no style debates |
129
+
130
+ ### Node.js >= 20
131
+
132
+ Minimum requirement: Node.js 20 LTS. Reasons:
133
+ - Native `node:test` (no Jest/Mocha)
134
+ - `node:crypto` with modern APIs
135
+ - Native `--watch` (no nodemon)
136
+ - Stable ESM
137
+ - Improved V8 performance
138
+
139
+ ---
140
+
141
+ ## 3. Directory Structure
142
+
143
+ ```
144
+ securelan-chat/
145
+
146
+ ├── docs/
147
+ │ └── ARCHITECTURE.md # Este documento
148
+
149
+ ├── src/
150
+ │ ├── server/
151
+ │ │ ├── index.js # Entry point do servidor
152
+ │ │ ├── WebSocketServer.js # Gerencia conexoes WebSocket
153
+ │ │ ├── SessionManager.js # Controla sessoes ativas (clientes conectados)
154
+ │ │ ├── MessageRouter.js # Roteia payloads cifrados entre clientes
155
+ │ │ ├── OfflineQueue.js # Fila de mensagens para peers offline
156
+ │ │ └── CertManager.js # Geracao e carregamento de certs TLS
157
+ │ │
158
+ │ ├── client/
159
+ │ │ ├── index.js # Entry point do cliente
160
+ │ │ ├── UI.js # Interface blessed (layout, rendering)
161
+ │ │ ├── Connection.js # Conexao WebSocket com o servidor
162
+ │ │ ├── ChatController.js # Logica central: conecta UI + Connection + Crypto
163
+ │ │ └── FileTransfer.js # Envio/recepcao de arquivos cifrados (chunks)
164
+ │ │
165
+ │ ├── crypto/
166
+ │ │ ├── KeyManager.js # Gera e gerencia pares de chaves (em memoria)
167
+ │ │ ├── MessageCrypto.js # Cifra e decifra mensagens (crypto_box_easy)
168
+ │ │ ├── Handshake.js # Protocolo de troca de chaves publicas
169
+ │ │ ├── NonceManager.js # Geracao e validacao de nonces
170
+ │ │ ├── DoubleRatchet.js # PFS via Double Ratchet (DH ratchet + KDF chains)
171
+ │ │ ├── TrustStore.js # TOFU + SAS (persistencia de fingerprints)
172
+ │ │ └── StateManager.js # Persistencia cifrada de estado (Argon2id + secretbox)
173
+ │ │
174
+ │ ├── p2p/
175
+ │ │ ├── index.js # Entry point do modo P2P
176
+ │ │ ├── Discovery.js # mDNS discovery via bonjour-service
177
+ │ │ ├── PeerServer.js # WebSocket server local (porta aleatoria)
178
+ │ │ ├── PeerConnectionManager.js # Gerencia conexoes outbound/inbound
179
+ │ │ └── P2PChatController.js # Orquestrador P2P (crypto + UI + peers)
180
+ │ │
181
+ │ ├── protocol/
182
+ │ │ ├── messages.js # Definicao dos tipos de mensagem do protocolo
183
+ │ │ └── validators.js # Validacao de estrutura dos payloads
184
+ │ │
185
+ │ └── shared/
186
+ │ ├── constants.js # Constantes globais (portas, limites, versao)
187
+ │ └── logger.js # Logger estruturado (com niveis e timestamps)
188
+
189
+ ├── test/
190
+ │ ├── crypto.test.js # Testes do modulo criptografico
191
+ │ ├── protocol.test.js # Testes de validacao do protocolo
192
+ │ ├── nonce.test.js # Testes do gerenciador de nonces
193
+ │ ├── integration.test.js # Testes de integracao E2E
194
+ │ ├── double-ratchet.test.js # Testes do Double Ratchet
195
+ │ ├── trust-store.test.js # Testes do TrustStore + SAS
196
+ │ ├── message-crypto.test.js # Testes do MessageCrypto (padding, encrypt)
197
+ │ └── state-manager.test.js # Testes de persistencia de estado
198
+
199
+ ├── scripts/
200
+ │ └── generate-fingerprint.js # Utilitario: gera fingerprint de chave publica
201
+
202
+ ├── .editorconfig
203
+ ├── .eslintrc.js
204
+ ├── .gitignore
205
+ ├── .npmrc
206
+ ├── .prettierrc
207
+ ├── jsconfig.json
208
+ ├── package.json
209
+ └── README.md
210
+ ```
211
+
212
+ ---
213
+
214
+ ## 4. Responsibility of Each Module
215
+
216
+ ### 4.1 Server
217
+
218
+ #### `src/server/index.js` — Entry Point
219
+ - Initializes the WebSocket server on the configured port (default: 3600)
220
+ - Registers connection/disconnection handlers
221
+ - Graceful shutdown (SIGINT, SIGTERM)
222
+ - Prints local network information (IP, port)
223
+
224
+ #### `src/server/WebSocketServer.js` — WebSocket Management
225
+ - Wrapper over `ws.WebSocketServer`
226
+ - Configures limits: `maxPayload` (64KB), heartbeat (ping/pong every 30s)
227
+ - Detects disconnected clients via heartbeat
228
+ - Emits typed events: `connection`, `message`, `close`, `error`
229
+ - Does not interpret content — treats everything as an opaque `Buffer`
230
+
231
+ #### `src/server/SessionManager.js` — Sessions
232
+ - Maintains a map of active sessions: `Map<sessionId, { ws, nickname, publicKey, connectedAt }>`
233
+ - Generates session IDs with `crypto.randomUUID()`
234
+ - Broadcasts the user list when someone joins/leaves
235
+ - Rejects duplicate nicknames
236
+ - Inactive session timeout (configurable)
237
+ - **Stores the public key only to redistribute it** — it is not a secret, it is an identity
238
+
239
+ #### `src/server/MessageRouter.js` — Routing
240
+ - Receives payloads of type `encrypted_message`
241
+ - Validates structure (has `to`, `from`, `payload`)
242
+ - **Does not open `payload`** — only checks the routing fields
243
+ - Forwards to the recipient's WebSocket
244
+ - If the recipient is offline and was recently disconnected, queues it in the OfflineQueue
245
+ - Returns an error if the recipient is not found and has no recent history
246
+ - Basic rate limiting: max 30 messages/second per client
247
+
248
+ #### `src/server/OfflineQueue.js` — Offline Queue
249
+ - Stores encrypted (opaque) messages for disconnected peers
250
+ - Keyed by nickname + publicKey (ensures it only delivers if the peer reconnects with the same key)
251
+ - Limits: max 100 msgs/peer, max 1000 total, max 1h of age
252
+ - If a peer reconnects with a different key (new client), the queue is discarded
253
+ - Periodic cleanup every 5min
254
+ - The queue is lost when the server restarts (in-memory)
255
+
256
+ ### 4.2 Client
257
+
258
+ #### `src/client/index.js` — Entry Point
259
+ - Asks the user for a nickname
260
+ - Asks for the server's IP:port (default: localhost:3600)
261
+ - Initializes the KeyManager (generates a key pair)
262
+ - Displays the public key fingerprint for verification
263
+ - Connects to the server and starts the UI
264
+
265
+ #### `src/client/UI.js` — Blessed Interface
266
+ - Layout divided into 3 areas:
267
+
268
+ ```
269
+ ┌─────────────────────────────────────────┐
270
+ │ SecureLAN Chat [3 online] E2E │ <- Header/Status bar
271
+ ├─────────────────────────────────────────┤
272
+ │ │
273
+ │ [10:30] Alice: Ola! │ <- Chat area (scrollable)
274
+ │ [10:31] Voce: Oi Alice! │
275
+ │ [10:32] * Bob entrou no chat │
276
+ │ [10:32] Bob: Fala galera │
277
+ │ │
278
+ ├─────────────────────────────────────────┤
279
+ │ > Digite sua mensagem... │ <- Input box
280
+ └─────────────────────────────────────────┘
281
+ ```
282
+
283
+ - The status bar shows: chat name, online users, E2E indicator
284
+ - Chat area with automatic and manual scroll
285
+ - Input with history (up/down arrows)
286
+ - Distinct colors per user (chalk)
287
+ - Special commands: `/quit`, `/users`, `/fingerprint`, `/clear`, `/file`, `/sound`, `/help`
288
+ - Animated "typing..." indicator with support for multiple peers
289
+ - Sound notifications (toggle via `/sound on|off`)
290
+ - Progress bar for file transfers
291
+ - Notifications for users joining/leaving
292
+
293
+ #### `src/client/Connection.js` — WebSocket Client
294
+ - Connects to the server via `ws`
295
+ - Automatic reconnect with exponential backoff (1s, 2s, 4s, 8s, max 30s)
296
+ - Ping/pong to detect disconnection
297
+ - Serializes/deserializes protocol messages (JSON for control, Buffer for encrypted data)
298
+ - Emits events to the ChatController
299
+
300
+ #### `src/client/ChatController.js` — Orchestrator
301
+ - Connects: UI <-> Connection <-> Crypto
302
+ - Send flow: UI input -> encrypt -> Connection send
303
+ - Receive flow: Connection receive -> decrypt -> UI display
304
+ - Manages state: peer list, received public keys, derived shared keys
305
+ - Performs the handshake with each new peer
306
+ - Validates fingerprints
307
+ - Manages file transfers via FileTransfer
308
+ - Sound notification when text messages are received
309
+
310
+ #### `src/client/FileTransfer.js` — File Transfer
311
+ - Sending: reads the file, splits it into 48KB chunks, encrypts each chunk E2E via broadcast
312
+ - Receiving: reassembles chunks, verifies SHA-256, saves to `./downloads/`
313
+ - Throttling: max 25 chunks/sec (below the rate limit of 30/sec)
314
+ - Timeout: 30s for incomplete transfers
315
+ - Support for files up to 50MB
316
+
317
+ ### 4.3 Crypto
318
+
319
+ #### `src/crypto/KeyManager.js` — Keys
320
+ - Generates a Curve25519 pair with `sodium.crypto_box_keypair()`
321
+ - Stores it in `sodium.sodium_malloc()` (secure memory, does not go to swap)
322
+ - Exports the public key as a `Buffer` (to send to the server)
323
+ - Generates a fingerprint: `SHA-256(publicKey)` formatted as `XXXX:XXXX:XXXX:XXXX`
324
+ - A `destroy()` method that calls `sodium.sodium_memzero()` on the keys
325
+ - **Never** serializes the private key to disk, log, or network
326
+
327
+ #### `src/crypto/MessageCrypto.js` — Encrypt/Decrypt
328
+ - **Encrypt**: `sodium.crypto_box_easy(message, nonce, theirPublicKey, mySecretKey)`
329
+ - **Decrypt**: `sodium.crypto_box_open_easy(ciphertext, nonce, theirPublicKey, mySecretKey)`
330
+ - The `crypto_box` function internally uses:
331
+ - **X25519**: Diffie-Hellman to derive the shared key
332
+ - **XSalsa20**: Stream cipher for confidentiality
333
+ - **Poly1305**: MAC for authenticity and integrity
334
+ - Returns `{ ciphertext: Buffer, nonce: Buffer }`
335
+ - Validates the minimum ciphertext size (16-byte MAC)
336
+
337
+ #### `src/crypto/Handshake.js` — Peer Management
338
+ - Registers peers' public keys received via the server
339
+ - Validates the public key size (32 bytes, Curve25519)
340
+ - Stores public keys in `Map<peerId, publicKey>`
341
+ - Exposes access to the local secret key for use in `crypto_box_easy`
342
+ - A `removePeer()` method for cleanup on disconnect
343
+ - A `destroy()` method to clear all state
344
+
345
+ #### `src/crypto/DoubleRatchet.js` — PFS (Perfect Forward Secrecy)
346
+ - A simplified implementation of the Double Ratchet (Signal-style)
347
+ - Each message uses a unique derived key, destroyed after use
348
+ - DH ratchet: `crypto_scalarmult` (X25519) to generate a new DH output each turn
349
+ - KDF_RK: `BLAKE2b-512(rootKey, dhOutput)` → new rootKey + chainKey
350
+ - KDF_CK: `BLAKE2b-256(chainKey, 0x01)` → messageKey; `BLAKE2b-256(chainKey, 0x02)` → nextChainKey
351
+ - Encryption: `crypto_secretbox_easy` (symmetric XSalsa20-Poly1305) with the derived messageKey
352
+ - Management of skipped keys (out-of-order messages) with a 60s TTL
353
+ - Immediate destruction of keys after use (`sodium_memzero`)
354
+ - Automatic fallback to static keys when the ratchet is unavailable
355
+
356
+ #### `src/crypto/TrustStore.js` — TOFU + SAS
357
+ - **TOFU (Trust On First Use)**: Persists peers' fingerprints in `.ciphermesh/trusted-peers.json`
358
+ - Detects a public key change (possible MITM) — similar to SSH's `known_hosts`
359
+ - **SAS (Short Authentication String)**: A 6-digit code for out-of-band verification
360
+ - `BLAKE2b-256(sortedPubKeys || "CipherMesh-SAS-v1")` → first 3 bytes → 6 decimal digits
361
+ - Both sides compute the same value independently
362
+ - TrustResult: `NEW_PEER` | `TRUSTED` | `MISMATCH` | `VERIFIED_MISMATCH`
363
+ - Authenticated E2E rotation (`autoUpdatePeer`) preserves the verification status
364
+ - Rotation via the server (unauthenticated) does NOT update the trust store
365
+
366
+ #### `src/crypto/StateManager.js` — Encrypted Persistence
367
+ - Persists session state in `.ciphermesh/state/session-state.enc.json`
368
+ - `deriveKEK(passphrase, salt?)` — Derives a Key Encryption Key with `crypto_pwhash` (Argon2id)
369
+ - `saveState(data, kek, salt)` — Encrypts state with `crypto_secretbox_easy`, saves the envelope `{salt, nonce, ciphertext}`
370
+ - `loadState(passphrase)` — Re-derives the KEK from the saved salt, decrypts, returns the object or `null`
371
+ - `hasState()` / `clearState()` — Checks existence / removes saved state
372
+ - Used to preserve ratchets, keys, and peers across reconnections
373
+
374
+ #### `src/crypto/NonceManager.js` — Nonces
375
+ - Generates 24-byte nonces with `sodium.randombytes_buf()`
376
+ - Maintains a **monotonically increasing counter** per peer
377
+ - Rejects repeated nonces or ones smaller than the last received (anti-replay)
378
+ - Nonce structure:
379
+
380
+ ```
381
+ [ 8 bytes: timestamp ms | 4 bytes: counter | 12 bytes: random ]
382
+ anti-replay sequencia unicidade
383
+ ```
384
+
385
+ ### 4.4 P2P (Alternative Mode)
386
+
387
+ #### `src/p2p/Discovery.js` — mDNS Discovery
388
+ - Publishes the `_ciphermesh._tcp` service via mDNS using `bonjour-service`
389
+ - TXT records: `{ nickname, publicKey, version }`
390
+ - Automatically searches for peers of the same type on the LAN
391
+ - Emits events: `peer-discovered`, `peer-lost`
392
+ - Ignores self (same nickname)
393
+
394
+ #### `src/p2p/PeerServer.js` — Local Server
395
+ - `WebSocketServer` listening on a random port (`port: 0`)
396
+ - Accepts inbound connections from peers on the LAN
397
+ - Emits `connection(ws)` for the controller to process
398
+
399
+ #### `src/p2p/PeerConnectionManager.js` — Connection Manager
400
+ - Manages outbound and inbound WebSocket connections
401
+ - **Deduplication**: The lexicographically smaller nickname initiates the connection
402
+ - If `alice < bob`: Alice connects, Bob waits
403
+ - Result: exactly 1 WebSocket between each pair
404
+ - P2P handshake: `{ type: "p2p_handshake", nickname, publicKey, version, timestamp }`
405
+ - Reconnect with exponential backoff (2s→30s) for outbound connections
406
+ - `send(nickname, data)` / `broadcast(data)` — sends to peer(s)
407
+
408
+ #### `src/p2p/P2PChatController.js` — P2P Orchestrator
409
+ - An adaptation of `ChatController` for peer-to-peer mode
410
+ - Uses the **nickname as the peer ID** (stable, vs. the ephemeral sessionId in server mode)
411
+ - Same crypto: DoubleRatchet, TOFU, SAS, key rotation, secure wipe
412
+ - Same commands: `/verify`, `/trust`, `/trustlist`, `/file`, etc.
413
+ - Main difference: messages go directly peer-to-peer, without a relay
414
+
415
+ | Aspect | Server Mode | P2P Mode |
416
+ |---------|---------------|----------|
417
+ | Connection | Single WS to the server | Direct WS between each pair of peers |
418
+ | Discovery | `JOIN_ACK` from the server | mDNS on the LAN |
419
+ | Peer ID | sessionId (ephemeral UUID) | nickname (stable) |
420
+ | Routing | Via the server (blind relay) | Direct peer-to-peer |
421
+ | Offline queue | Yes (the server stores it) | No (peers must be online) |
422
+
423
+ #### `src/p2p/index.js` — P2P Entry Point
424
+ - Prompt: nickname, passphrase (optional, to restore state)
425
+ - Initializes `PeerServer` (random port) + `Discovery` (mDNS)
426
+ - `PeerConnectionManager` + `P2PChatController` + `UI`
427
+ - Shutdown: saves encrypted state if a passphrase is set
428
+
429
+ ### 4.5 Protocol (Server Mode)
430
+
431
+ #### `src/protocol/messages.js` — Message Types
432
+
433
+ Defines the protocol's message types. All messages have:
434
+ ```js
435
+ {
436
+ type: string, // tipo da mensagem
437
+ version: 1, // versao do protocolo
438
+ timestamp: number // Date.now() do remetente
439
+ }
440
+ ```
441
+
442
+ Types:
443
+ | Type | Direction | Description |
444
+ |------|---------|-----------|
445
+ | `join` | Client -> Server | The client wants to join (nickname + publicKey) |
446
+ | `join_ack` | Server -> Client | The server confirms the join (sessionId + peer list) |
447
+ | `peer_joined` | Server -> Clients | A new peer joined (nickname + publicKey) |
448
+ | `peer_left` | Server -> Clients | A peer left |
449
+ | `key_exchange` | Client -> Server -> Client | Public key exchange between peers |
450
+ | `encrypted_message` | Client -> Server -> Client | Encrypted message |
451
+ | `error` | Server -> Client | Error (duplicate nickname, etc.) |
452
+ | `ping` / `pong` | Bidirectional | Heartbeat |
453
+
454
+ #### `src/protocol/validators.js` — Validation
455
+ - Validates the JSON structure of each message type
456
+ - Checks required fields and types
457
+ - Checks maximum sizes (nickname: 20 chars, payload: 64KB)
458
+ - Sanitizes inputs (trim, removal of control characters)
459
+ - Rejects messages with an incompatible `version`
460
+
461
+ ### 4.6 Shared
462
+
463
+ #### `src/shared/constants.js` — Constants
464
+
465
+ ```js
466
+ export const SERVER_PORT = 3600;
467
+ export const MAX_NICKNAME_LENGTH = 20;
468
+ export const MAX_PAYLOAD_SIZE = 65536; // 64KB
469
+ export const HEARTBEAT_INTERVAL_MS = 30000; // 30s
470
+ export const RECONNECT_BASE_MS = 1000; // 1s
471
+ export const RECONNECT_MAX_MS = 30000; // 30s
472
+ export const RATE_LIMIT_PER_SECOND = 30;
473
+ export const SESSION_TIMEOUT_MS = 300000; // 5min inativo
474
+ export const PROTOCOL_VERSION = 1;
475
+ export const NONCE_SIZE = 24; // libsodium nonce
476
+ export const PUBLIC_KEY_SIZE = 32; // Curve25519
477
+ export const SECRET_KEY_SIZE = 32;
478
+ export const MAC_SIZE = 16; // Poly1305
479
+
480
+ // Offline queue
481
+ export const OFFLINE_QUEUE_MAX_PER_PEER = 100;
482
+ export const OFFLINE_QUEUE_MAX_AGE_MS = 3600000; // 1h
483
+ export const OFFLINE_QUEUE_MAX_TOTAL = 1000;
484
+
485
+ // File transfer
486
+ export const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
487
+ export const FILE_CHUNK_SIZE = 49152; // 48KB
488
+ ```
489
+
490
+ #### `src/shared/logger.js` — Logger
491
+ - Levels: `debug`, `info`, `warn`, `error`
492
+ - Format: `[HH:MM:SS] [LEVEL] [module] message`
493
+ - Level configurable via the `LOG_LEVEL` environment variable
494
+ - Never logs message content or private keys
495
+ - Logs only metadata: connections, disconnections, errors
496
+
497
+ ---
498
+
499
+ ## 5. Data Model and Payloads
500
+
501
+ ### 5.1 Join Message (client -> server)
502
+
503
+ ```json
504
+ {
505
+ "type": "join",
506
+ "version": 1,
507
+ "timestamp": 1739800000000,
508
+ "nickname": "Alice",
509
+ "publicKey": "base64(32 bytes da chave publica Curve25519)"
510
+ }
511
+ ```
512
+
513
+ ### 5.2 Join ACK (server -> client)
514
+
515
+ ```json
516
+ {
517
+ "type": "join_ack",
518
+ "version": 1,
519
+ "timestamp": 1739800000050,
520
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000",
521
+ "peers": [
522
+ {
523
+ "sessionId": "660e8400-e29b-41d4-a716-446655440001",
524
+ "nickname": "Bob",
525
+ "publicKey": "base64(chave publica do Bob)"
526
+ }
527
+ ]
528
+ }
529
+ ```
530
+
531
+ ### 5.3 Encrypted Message (client -> server -> client)
532
+
533
+ ```json
534
+ {
535
+ "type": "encrypted_message",
536
+ "version": 1,
537
+ "timestamp": 1739800001000,
538
+ "from": "550e8400-e29b-41d4-a716-446655440000",
539
+ "to": "660e8400-e29b-41d4-a716-446655440001",
540
+ "payload": {
541
+ "ciphertext": "base64(mensagem cifrada com crypto_box_easy)",
542
+ "nonce": "base64(24 bytes do nonce usado)"
543
+ }
544
+ }
545
+ ```
546
+
547
+ **Note**: The `payload` field is completely opaque to the server. It only reads `from` and `to` for routing.
548
+
549
+ ### 5.4 Decrypted content (never travels in cleartext)
550
+
551
+ After decrypting `payload.ciphertext`, the result is:
552
+
553
+ ```json
554
+ {
555
+ "text": "Ola Bob, tudo bem?",
556
+ "sentAt": 1739800001000,
557
+ "messageId": "a1b2c3d4"
558
+ }
559
+ ```
560
+
561
+ - `sentAt` inside the encrypted payload lets the receiver validate against the external `timestamp`
562
+ - `messageId` is a short random ID for reference (not a UUID, just 4 bytes of hex)
563
+
564
+ #### Encrypted commands (actions)
565
+
566
+ Besides text messages, the encrypted payload may contain commands (the `action` field):
567
+
568
+ ```json
569
+ { "action": "clear", "sentAt": 1739800001000 }
570
+ ```
571
+
572
+ ```json
573
+ { "action": "typing", "sentAt": 1739800001000 }
574
+ ```
575
+
576
+ - `clear` — clears the chat for all peers
577
+ - `typing` — indicates that the sender is typing (2s debounce, expires after 3s at the receiver)
578
+ - `file_offer` — offers to send a file (transferId, fileName, fileSize, totalChunks, sha256)
579
+ - `file_chunk` — sends a file chunk (transferId, chunkIndex, base64 data)
580
+ - `file_complete` — signals the end of the transfer (transferId)
581
+
582
+ ### 5.5 Peer Notification (server -> clients)
583
+
584
+ ```json
585
+ {
586
+ "type": "peer_joined",
587
+ "version": 1,
588
+ "timestamp": 1739800002000,
589
+ "peer": {
590
+ "sessionId": "770e8400-e29b-41d4-a716-446655440002",
591
+ "nickname": "Charlie",
592
+ "publicKey": "base64(chave publica do Charlie)"
593
+ }
594
+ }
595
+ ```
596
+
597
+ ### 5.6 Error (server -> client)
598
+
599
+ ```json
600
+ {
601
+ "type": "error",
602
+ "version": 1,
603
+ "timestamp": 1739800003000,
604
+ "code": "NICKNAME_TAKEN",
605
+ "message": "O nickname 'Alice' ja esta em uso"
606
+ }
607
+ ```
608
+
609
+ Error codes:
610
+ | Code | Description |
611
+ |--------|-----------|
612
+ | `NICKNAME_TAKEN` | Nickname already in use |
613
+ | `INVALID_MESSAGE` | Invalid message structure |
614
+ | `PEER_NOT_FOUND` | The recipient is not online |
615
+ | `RATE_LIMITED` | Too many messages per second |
616
+ | `PAYLOAD_TOO_LARGE` | Payload exceeds 64KB |
617
+
618
+ ---
619
+
620
+ ## 6. Detailed Cryptographic Flow
621
+
622
+ ### 6.1 Algorithms Used
623
+
624
+ | Operation | Algorithm | Size | Description |
625
+ |----------|-----------|---------|-----------|
626
+ | Key pair | **Curve25519** | 32 bytes each | Elliptic curve for key exchange |
627
+ | Key exchange | **X25519** (ECDH) | 32-byte result | Diffie-Hellman over Curve25519 |
628
+ | Cipher | **XSalsa20** | stream cipher | A variant of Salsa20 with a 24-byte nonce |
629
+ | MAC | **Poly1305** | 16-byte tag | Message Authentication Code |
630
+ | Nonce | random + counter | 24 bytes | Number used only once |
631
+
632
+ ### 6.2 Why Curve25519 + XSalsa20-Poly1305?
633
+
634
+ This combination (known as **NaCl crypto_box**) was chosen because:
635
+
636
+ 1. **Modern curve**: Curve25519 was designed by Daniel J. Bernstein to be resistant to timing attacks and to have secure implementations
637
+ 2. **Authenticated encryption**: XSalsa20-Poly1305 combines confidentiality + integrity in a single atomic operation (AEAD)
638
+ 3. **24-byte nonce**: Large enough to be generated randomly with no practical risk of collision (2^192 combinations)
639
+ 4. **No padding oracle**: A stream cipher needs no padding, eliminating an entire class of attacks
640
+ 5. **Misuse-resistant**: Hard to use incorrectly (compared to AES-CBC, manual AES-CTR, etc.)
641
+
642
+ ### 6.3 Key Generation
643
+
644
+ ```
645
+ 1. Cliente inicia
646
+ 2. sodium.crypto_box_keypair() gera:
647
+ - publicKey: 32 bytes (pode ser compartilhada)
648
+ - secretKey: 32 bytes (NUNCA sai da memoria do processo)
649
+ 3. Ambas armazenadas em sodium.sodium_malloc() (secure memory)
650
+ 4. Fingerprint = SHA256(publicKey) formatada como XXXX:XXXX:XXXX:XXXX
651
+ ```
652
+
653
+ ### 6.4 Authenticated Encryption with crypto_box_easy
654
+
655
+ `crypto_box_easy` does everything internally in a single atomic operation:
656
+
657
+ ```
658
+ crypto_box_easy(ciphertext, plaintext, nonce, recipientPublicKey, senderSecretKey)
659
+
660
+ Internamente:
661
+ 1. X25519 DH: sharedSecret = ECDH(recipientPub, senderSec)
662
+ 2. Key derivation: encKey = HSalsa20(sharedSecret, zeros)
663
+ 3. Cifra: XSalsa20(plaintext, nonce, encKey) -> ciphertext
664
+ 4. MAC: Poly1305(ciphertext) -> tag de 16 bytes
665
+ 5. Output: tag || ciphertext (autenticado)
666
+ ```
667
+
668
+ The shared key is derived implicitly on each call. The DH guarantees
669
+ that both sides (Alice and Bob) arrive at the same secret without exchanging it over the network.
670
+
671
+ ### 6.5 Message Encryption
672
+
673
+ ```
674
+ Input:
675
+ - plaintext: Buffer (mensagem em UTF-8)
676
+ - nonce: 24 bytes (gerado pelo NonceManager)
677
+ - recipientPublicKey: 32 bytes (chave publica do destinatario)
678
+ - senderSecretKey: 32 bytes (chave secreta do remetente)
679
+
680
+ Processo:
681
+ ciphertext = crypto_box_easy(plaintext, nonce, recipientPublicKey, senderSecretKey)
682
+
683
+ Output:
684
+ - ciphertext: Buffer (plaintext.length + 16 bytes de MAC)
685
+ - nonce: 24 bytes (enviado junto, nao e segredo)
686
+
687
+ Total enviado: ciphertext (N+16 bytes) + nonce (24 bytes)
688
+ ```
689
+
690
+ ### 6.6 Message Decryption
691
+
692
+ ```
693
+ Input:
694
+ - ciphertext: Buffer (recebido da rede)
695
+ - nonce: 24 bytes (recebido da rede)
696
+ - senderPublicKey: 32 bytes (chave publica do remetente)
697
+ - recipientSecretKey: 32 bytes (chave secreta do destinatario)
698
+
699
+ Processo:
700
+ 1. NonceManager valida que nonce nao foi usado antes (anti-replay)
701
+ 2. plaintext = crypto_box_open_easy(ciphertext, nonce, senderPublicKey, recipientSecretKey)
702
+ 3. Se MAC invalido -> rejeita (mensagem foi adulterada)
703
+ 4. Se MAC valido -> parse do JSON interno
704
+
705
+ Output:
706
+ - plaintext: Buffer (mensagem original)
707
+ ```
708
+
709
+ ### 6.7 Nonce Structure (24 bytes)
710
+
711
+ ```
712
+ ┌──────────────────┬──────────────┬──────────────────────┐
713
+ │ Timestamp (8B) │ Counter (4B) │ Random (12B) │
714
+ │ ms desde epoch │ sequencial │ sodium.randombytes │
715
+ └──────────────────┴──────────────┴──────────────────────┘
716
+
717
+ - Timestamp: impede replay entre sessoes diferentes
718
+ - Counter: garante ordenacao e unicidade dentro da sessao
719
+ - Random: garante unicidade mesmo com clocks sincronizados
720
+ ```
721
+
722
+ ### 6.8 Fingerprint Verification
723
+
724
+ The fingerprint lets users verify each other's identity **out of band** (for example, in person or by phone):
725
+
726
+ ```
727
+ 1. Alice ve seu fingerprint: A1B2:C3D4:E5F6:7890
728
+ 2. Bob ve o fingerprint de Alice: A1B2:C3D4:E5F6:7890
729
+ 3. Bob confirma pessoalmente com Alice que os valores batem
730
+ 4. Se nao baterem -> MITM detectado
731
+ ```
732
+
733
+ The fingerprint is computed like this:
734
+ ```
735
+ fingerprint = SHA-256(publicKey)
736
+ = primeiros 8 bytes, formatados em hex com separador ':'
737
+ = "A1B2:C3D4:E5F6:7890"
738
+ ```
739
+
740
+ ---
741
+
742
+ ## 7. Handshake Protocol
743
+
744
+ ### 7.1 Full Diagram
745
+
746
+ ```
747
+ Cliente A Servidor Cliente B
748
+ │ │ │
749
+ │ 1. JOIN(nick, pubKeyA) │ │
750
+ │ ──────────────────────────>│ │
751
+ │ │ │
752
+ │ 2. JOIN_ACK(sessionId, │ │
753
+ │ peers=[B: pubKeyB]) │ │
754
+ │ <──────────────────────────│ │
755
+ │ │ │
756
+ │ │ 3. PEER_JOINED(A, pubKeyA)│
757
+ │ │───────────────────────────>│
758
+ │ │ │
759
+ │ 4. Deriva sharedKey(A,B) │ 5. Deriva sharedKey(B,A)
760
+ │ usando pubKeyB + secKeyA │ usando pubKeyA + secKeyB
761
+ │ │ │
762
+ │ 6. ENCRYPTED_MSG ─────────│────────────────────────> │
763
+ │ │ 7. Decifra com sharedKey│
764
+ │ │ │
765
+ ```
766
+
767
+ ### 7.2 Detailed Steps
768
+
769
+ **Step 1 — JOIN**: Client A generates a key pair and sends `{ type: "join", nickname: "Alice", publicKey: base64(pubKeyA) }` to the server.
770
+
771
+ **Step 2 — JOIN_ACK**: The server validates the nickname (unique?), registers the session, and returns the list of already-connected peers with their public keys.
772
+
773
+ **Step 3 — PEER_JOINED**: The server notifies all existing clients that Alice joined, including her public key.
774
+
775
+ **Steps 4 and 5 — Derivation**: Each side computes `crypto_box_beforenm()` with the other's public key and its own private key. Result: the same shared key.
776
+
777
+ **Step 6 — Encrypted message**: Alice encrypts and sends. The server relays without reading.
778
+
779
+ **Step 7 — Decryption**: Bob decrypts with the derived shared key.
780
+
781
+ ### 7.3 Handshake Security
782
+
783
+ - The public key travels in cleartext (this is safe — it is public by definition)
784
+ - The private key **never** leaves the client's process
785
+ - The server sees public keys but cannot derive the shared key (it would need a private key)
786
+ - An attacker who captures all traffic sees only public keys + encrypted payloads = useless without a private key
787
+
788
+ **PFS implemented**: The system uses a simplified Double Ratchet for live conversations. Each message uses a unique derived key, destroyed after use. Compromising one key reveals at most ONE message. Offline messages use static keys as a fallback.
789
+
790
+ ---
791
+
792
+ ## 8. Step-by-Step Communication Flow
793
+
794
+ ### 8.1 Full Scenario: Alice sends "Ola" to Bob
795
+
796
+ ```
797
+ TEMPO ACAO
798
+ ───── ──────────────────────────────────────────────────────
799
+ t0 Alice digita "Ola" no input e pressiona Enter
800
+
801
+ t1 ChatController recebe o texto da UI
802
+ ChatController verifica se tem sharedKey com Bob
803
+ Se nao tem -> erro "Handshake nao completado com Bob"
804
+
805
+ t2 MessageCrypto.encrypt():
806
+ - NonceManager gera nonce de 24 bytes
807
+ - Serializa payload interno: { text: "Ola", sentAt: t2, messageId: "a1b2" }
808
+ - crypto_box_easy_afternm(payload, nonce, sharedKeyAB)
809
+ - Retorna { ciphertext: Buffer, nonce: Buffer }
810
+
811
+ t3 Connection envia ao servidor:
812
+ {
813
+ type: "encrypted_message",
814
+ from: "alice-session-id",
815
+ to: "bob-session-id",
816
+ payload: { ciphertext: "base64(...)", nonce: "base64(...)" }
817
+ }
818
+
819
+ t4 Servidor (MessageRouter):
820
+ - Valida estrutura (tem type, from, to, payload)
821
+ - NAO abre payload
822
+ - Encontra WebSocket do Bob pelo sessionId
823
+ - Encaminha o JSON inteiro para Bob
824
+
825
+ t5 Bob (Connection) recebe o JSON
826
+ ChatController identifica: encrypted_message de Alice
827
+
828
+ t6 MessageCrypto.decrypt():
829
+ - Extrai ciphertext e nonce do payload
830
+ - NonceManager valida nonce (nao repetido, counter valido)
831
+ - crypto_box_open_easy_afternm(ciphertext, nonce, sharedKeyAB)
832
+ - Se MAC falhar -> rejeita (mensagem corrompida/adulterada)
833
+ - Se MAC ok -> parse do JSON interno
834
+
835
+ t7 ChatController recebe { text: "Ola", sentAt: t2, messageId: "a1b2" }
836
+ Valida que sentAt e razoavel (nao muito no passado/futuro)
837
+
838
+ t8 UI.displayMessage("Alice", "Ola", timestamp)
839
+ Bob ve: [10:30] Alice: Ola
840
+ ```
841
+
842
+ ### 8.2 Scenario: Group Chat (broadcast)
843
+
844
+ To send to everyone, Alice encrypts **individually** for each peer:
845
+
846
+ ```
847
+ Alice -> Bob: encrypt(msg, nonceAB, sharedKeyAB)
848
+ Alice -> Charlie: encrypt(msg, nonceAC, sharedKeyAC)
849
+ ```
850
+
851
+ Each message has a different nonce and ciphertext because each shared key is different. The server receives N messages and routes each one to the correct recipient.
852
+
853
+ **Impact**: O(N) encryptions per group message. Acceptable for a LAN with few users.
854
+
855
+ **Sender keys (real groups) — `src/crypto/SenderKey.js`**: an O(1) alternative. Each
856
+ sender has a *sender key chain* (symmetric BLAKE2b ratchet) per room; the
857
+ message is encrypted **once** (`crypto_secretbox` + length padding) and the
858
+ **same** ciphertext is valid for all members. The chain is distributed once to
859
+ each member over the pairwise channel (never in cleartext). Forward secrecy: the chain
860
+ advances with each message and is rotated (`rotate()`) on member changes (with
861
+ redistribution). It supports out-of-order delivery (skipped keys, limited to
862
+ `maxSkip`) and rejects replay (an already-consumed counter) and tampering (Poly1305 MAC).
863
+
864
+ **Live integration (P2P)**: normal room messages use sender keys — encrypted
865
+ once (`p2p_group`) and sent identically to all peers in the room. The sender key
866
+ is distributed (`sk_dist`) over the pairwise channel when joining the room / when a peer
867
+ joins my room; since it goes over the same TCP connection, it arrives ordered before
868
+ any group message. When a peer leaves the room, my sender key is
869
+ rotated and redistributed (forward secrecy). Deniable, ephemeral, DMs,
870
+ store-and-forward, and cover-constant remain on the pairwise path because they have their own
871
+ semantics. (On the relay, the same model would give O(1) multicast; it requires
872
+ a server change and is not wired up there yet.)
873
+
874
+ ---
875
+
876
+ ## 9. Initialization Strategy
877
+
878
+ ### 9.1 Server
879
+
880
+ ```
881
+ 1. Carregar constantes (constants.js)
882
+ 2. Criar instancia WebSocketServer na porta configurada
883
+ 3. Criar SessionManager (mapa vazio de sessoes)
884
+ 4. Criar MessageRouter (referencia ao SessionManager)
885
+ 5. Registrar handlers:
886
+ - on('connection') -> SessionManager.handleConnection()
887
+ - on('close') -> SessionManager.handleDisconnection()
888
+ - on('message') -> MessageRouter.route()
889
+ 6. Iniciar heartbeat interval (ping todos os clientes a cada 30s)
890
+ 7. Registrar SIGINT/SIGTERM para graceful shutdown:
891
+ - Notificar todos os clientes
892
+ - Fechar conexoes
893
+ - Limpar recursos
894
+ 8. Imprimir no console:
895
+ - IP local (todas as interfaces de rede)
896
+ - Porta
897
+ - "Servidor pronto. Clientes podem conectar em ws://<IP>:3600"
898
+ ```
899
+
900
+ ### 9.2 Client
901
+
902
+ ```
903
+ 1. Exibir banner "SecureLAN Chat v1.0"
904
+ 2. Pedir nickname (validar: 1-20 chars, alfanumerico + underscore)
905
+ 3. Pedir endereco do servidor (default: localhost:3600)
906
+ 4. Gerar par de chaves (KeyManager)
907
+ 5. Exibir fingerprint da chave publica
908
+ 6. Conectar ao servidor via WebSocket
909
+ 7. Enviar mensagem JOIN (nickname + publicKey)
910
+ 8. Aguardar JOIN_ACK
911
+ 9. Se erro (nickname duplicado) -> pedir outro nickname
912
+ 10. Receber lista de peers e derivar sharedKey com cada um
913
+ 11. Inicializar UI blessed
914
+ 12. Exibir lista de usuarios online
915
+ 13. Entrar no loop de input
916
+ 14. Registrar handler de SIGINT para:
917
+ - sodium_memzero() em todas as chaves
918
+ - Fechar conexao WebSocket
919
+ - Destruir UI blessed
920
+ ```
921
+
922
+ ---
923
+
924
+ ## 10. Security — Threat Analysis
925
+
926
+ ### 10.1 Threat Model
927
+
928
+ | Threat | Mitigation | Status |
929
+ |--------|-----------|--------|
930
+ | **Malicious server reads messages** | Impossible — the server has no private key | Mitigated |
931
+ | **Server alters messages** | The Poly1305 MAC detects tampering | Mitigated |
932
+ | **Message replay** | Nonce with timestamp + monotonically increasing counter | Mitigated |
933
+ | **Man-in-the-Middle (MITM)** | TOFU + SAS (6-digit code) + fingerprint | Mitigated |
934
+ | **Private key leak** | Secure memory (sodium_malloc + mlock) | Mitigated |
935
+ | **Plaintext in memory** | Secure wipe (sodium_memzero) after use | Mitigated |
936
+ | **Private key in swap** | sodium_malloc() with mlock | Mitigated |
937
+ | **Brute force on the key** | Curve25519 = 128 bits of security (~3x10^38 operations) | Infeasible |
938
+ | **Nonce reuse** | Hybrid nonce (timestamp + counter + random) | Mitigated |
939
+ | **Denial of Service** | Rate limiting + maxPayload | Partial |
940
+ | **Forward secrecy** | Double Ratchet — a unique key per message | Mitigated |
941
+ | **Metadata analysis** | Message padding + fixed sizes | Partial |
942
+
943
+ ### 10.2 What the server CAN deduce (metadata)
944
+
945
+ Even without reading content, the server knows:
946
+ - **Who** is online
947
+ - **Who** talks to whom
948
+ - **When** messages are sent — *mitigated by cover traffic (optional)*
949
+ - **Approximate size** of messages — *mitigated: only the padding bucket leaks*
950
+ - **Frequency** of communication — *mitigated by cover traffic (optional)*
951
+
952
+ Implemented mitigations:
953
+ - **Length padding** (`MessageCrypto.padMessage`, buckets
954
+ `[128..32768]`): applied on all three encryption paths (static, ratchet, and
955
+ deniable) before encrypting. The server sees only which bucket, not the real size.
956
+ - **File chunk padding** (`FileTransfer`): the last (partial) chunk is
957
+ padded up to the full size with random bytes, so all chunks have the
958
+ same size on the wire; the receiver truncates it back using `fileSize`. This hides
959
+ the exact file size (only the chunk-rounded size leaks).
960
+ - **Cover traffic** (`/cover`, `src/shared/coverTraffic.js`): encrypted
961
+ decoy messages (`action: 'cover'`) with random filler, indistinguishable from
962
+ real messages; the recipient discards them silently. Two modes:
963
+ - `on` (jitter): decoys at random intervals (20-60s) — a baseline of noise.
964
+ - `constant`: a fixed-rate channel (~3s) — each slot carries a queued real
965
+ message or, if there is none, a decoy. The wire keeps an identical cadence
966
+ whether you are chatting or idle (cost: up to ~3s of latency per message).
967
+ This masks *when/how often* you chat, not *with whom*.
968
+ - **Migrating to P2P** eliminates the central server (but exposes IPs on the LAN).
969
+
970
+ - **Sealed sender** (`src/crypto/SealedSender.js`): removes the `from` from the
971
+ network envelope. The sender's identity goes inside a libsodium *sealed box*
972
+ (`crypto_box_seal` — anonymous encryption with an ephemeral key to the recipient's
973
+ public key), which only the recipient opens. The relay routes only by `to` and does not
974
+ see who sent it. The primitive is implemented and tested; wiring it into the envelope
975
+ (client seals / server routes without `from` / recipient opens and decrypts the
976
+ inner content) is the next step.
977
+
978
+ Still leaking: who is online and who *receives* (the `to`, inherent to star
979
+ routing). With sealed sender, the *sender* side of the social graph stays hidden.
980
+
981
+ ### 10.3 Resistance to Known Attacks
982
+
983
+ | Attack | Resistant? | Explanation |
984
+ |--------|-------------|-----------|
985
+ | Padding oracle | Yes | XSalsa20 is a stream cipher, no padding |
986
+ | Timing attack | Yes | libsodium uses constant-time comparisons |
987
+ | Chosen ciphertext | Yes | Poly1305 authenticates before decrypting |
988
+ | Key confusion | Yes | crypto_box uses typed keys (pub/sec) |
989
+ | Nonce misuse | Partial | The hybrid nonce reduces risk, but XSalsa20 is not nonce-misuse-resistant (unlike AES-GCM-SIV) |
990
+
991
+ ---
992
+
993
+ ## 11. Future Improvements
994
+
995
+ ### 11.1 Perfect Forward Secrecy (PFS) — IMPLEMENTED
996
+
997
+ **Implementation**: A simplified Double Ratchet using libsodium primitives.
998
+
999
+ **Hybrid model**:
1000
+ - **Ratchet** for live conversations (both peers online) — each message uses a unique key
1001
+ - **Static keys** (`crypto_box`) as a fallback for the offline queue and initial msgs
1002
+
1003
+ **Primitives used**:
1004
+ - `crypto_scalarmult` — raw X25519 DH for ratchet steps
1005
+ - `crypto_generichash` (BLAKE2b) — KDF to derive root keys and chain keys
1006
+ - `crypto_secretbox_easy` — Symmetric encryption with a per-message derived key
1007
+ - `sodium_malloc` / `sodium_memzero` — Secure memory, immediate destruction of keys
1008
+
1009
+ **Files**:
1010
+ - `src/crypto/DoubleRatchet.js` — The ratchet's main class (KDF_RK, KDF_CK, encrypt, decrypt, skipped keys)
1011
+ - Integrated into `Handshake.js` (per-peer ratchet management) and `ChatController.js` (wiring)
1012
+
1013
+ **Wire format**: Ratcheted msgs include `ephemeralPublicKey`, `counter`, `previousCounter` in the payload. Static msgs have no `ephemeralPublicKey`. The server is unaffected (opaque relay).
1014
+
1015
+ **Anti-replay**: The ratchet uses its own counter (no NonceManager needed). Static msgs continue to use the NonceManager.
1016
+
1017
+ ### 11.2 TOFU + SAS (Identity Verification) — IMPLEMENTED
1018
+
1019
+ **TOFU (Trust On First Use)**: When connecting with a peer for the first time, the public key fingerprint is saved locally in `.ciphermesh/trusted-peers.json`. On subsequent connections, if the key changes, the user is alerted (similar to SSH's `known_hosts`).
1020
+
1021
+ **SAS (Short Authentication String)**: A 6-digit code that both sides compute independently. It enables out-of-band verification (by voice, in person) to confirm there is no MITM.
1022
+
1023
+ **Commands**:
1024
+ - `/verify <nick>` — Shows the 6-digit SAS code
1025
+ - `/verify-confirm <nick>` — Marks a peer as verified after confirming the SAS
1026
+ - `/trust <nick>` — Accepts a peer's new key (resets verification)
1027
+ - `/trustlist` — Trust status of all online peers
1028
+
1029
+ **Trust model for key rotation**:
1030
+ - Authenticated E2E rotation (via the encrypted channel, action `key_rotation`) → `autoUpdatePeer()` preserves the `verified` status
1031
+ - Rotation via the server (`PEER_KEY_UPDATED`) → does NOT update the trust store (unauthenticated, potential MITM)
1032
+
1033
+ ### 11.3 Secure Memory Wipe — IMPLEMENTED
1034
+
1035
+ **Problem**: After decrypting, the plaintext remained in a normal `Buffer.alloc()`, subject to swap and GC.
1036
+
1037
+ **Solution**:
1038
+ - `unpadSecure(padded)`: Copies the plaintext to `sodium_malloc()`, wipes the original padded buffer
1039
+ - `encrypt()` in MessageCrypto and DoubleRatchet: `sodium_memzero(padded)` after encryption
1040
+ - `decrypt()` in DoubleRatchet: `sodium_memzero(padded)` in case of an invalid MAC
1041
+ - `ChatController.#onEncryptedMessage`: `finally { sodium_memzero(plaintext) }` after processing
1042
+
1043
+ **Limitation**: JS strings (`toString('utf-8')`, `JSON.parse()`) CANNOT be wiped — the V8 GC controls their lifetime. Only data in a `Buffer` is wiped.
1044
+
1045
+ ### 11.4 Reconnect with State — IMPLEMENTED
1046
+
1047
+ **Problem**: On disconnecting and reconnecting, the ratchets and keys were lost, forcing renegotiation.
1048
+
1049
+ **Solution**: `StateManager` persists encrypted state with the user's passphrase.
1050
+
1051
+ **Flow**:
1052
+ ```
1053
+ Startup:
1054
+ 1. Se existe estado salvo → prompt passphrase → loadState() → restaura KeyManager, Handshake, peers
1055
+ 2. Se nao existe → prompt passphrase opcional (para proteger sessao futura)
1056
+
1057
+ Shutdown (Ctrl+C, /quit):
1058
+ Se passphrase definida → serializeState() → saveState() cifrado
1059
+ ```
1060
+
1061
+ **State encryption**:
1062
+ - KDF: `crypto_pwhash` (Argon2id) with ops=3, mem=256MB → 32-byte KEK
1063
+ - Cipher: `crypto_secretbox_easy` (symmetric XSalsa20-Poly1305) with the KEK
1064
+ - Envelope: `{ salt, nonce, ciphertext }` in JSON
1065
+
1066
+ **Serialization**:
1067
+ - `DoubleRatchet.serialize()` / `deserialize()` — all private fields in base64, secrets in `sodium_malloc`
1068
+ - `KeyManager.serialize()` / `deserialize()` — publicKey + secretKey
1069
+ - `Handshake.serializeState()` / `restoreState()` — ratchets + peerKeys + mySessionId
1070
+ - `Handshake.migrateRatchet(oldId, newId)` — re-maps the ratchet when the sessionId changes on reconnect
1071
+
1072
+ ### 11.5 P2P with mDNS — IMPLEMENTED
1073
+
1074
+ **Alternative mode** (`npm run p2p`) that eliminates the central server using discovery via mDNS on the LAN.
1075
+
1076
+ **Architecture**:
1077
+ ```
1078
+ LAN (mDNS)
1079
+ ┌────── _ciphermesh._tcp ──────┐
1080
+ │ │
1081
+ ┌─────┴─────┐ ┌─────┴─────┐
1082
+ │ Peer A │◄──── WS ──────►│ Peer B │
1083
+ │ PeerServer│ direto │ PeerServer│
1084
+ │ :random │ │ :random │
1085
+ └───────────┘ └───────────┘
1086
+ ```
1087
+
1088
+ **Components**:
1089
+ - `Discovery.js` — publishes/searches the `_ciphermesh._tcp` service via `bonjour-service` (pure JS, Windows-compatible)
1090
+ - `PeerServer.js` — WebSocket server on a random port for inbound connections
1091
+ - `PeerConnectionManager.js` — manages all connections + deduplication
1092
+ - `P2PChatController.js` — same crypto (DoubleRatchet, TOFU, SAS, key rotation)
1093
+
1094
+ **Connection deduplication**: When Alice and Bob discover each other simultaneously via mDNS, the peer with the lexicographically smaller nickname initiates. Result: exactly 1 WebSocket between each pair.
1095
+
1096
+ **P2P protocol**:
1097
+ - `p2p_handshake`: `{ type, nickname, publicKey, version, timestamp }` — exchanged when the WebSocket opens
1098
+ - `p2p_message`: `{ type, payload: { ciphertext, nonce, ephemeralPublicKey?, counter?, previousCounter? } }` — the same encrypted format
1099
+
1100
+ **Future evolution** — P2P with a DHT (for larger networks):
1101
+ ```
1102
+ 1. Distributed Hash Table para discovery
1103
+ 2. Cada no mantem tabela de roteamento parcial
1104
+ 3. Mensagens podem ser roteadas por multiplos hops
1105
+ 4. Redundancia e tolerancia a falhas
1106
+ ```
1107
+
1108
+ ### 11.6 Professional Open-Source Project
1109
+
1110
+ **Repository structure**:
1111
+ ```
1112
+ securelan-chat/
1113
+ ├── .github/
1114
+ │ ├── workflows/
1115
+ │ │ ├── ci.yml # CI: lint + test em cada PR
1116
+ │ │ ├── release.yml # Release automatica com tags
1117
+ │ │ └── security-audit.yml # npm audit semanal
1118
+ │ ├── ISSUE_TEMPLATE/
1119
+ │ │ ├── bug_report.md
1120
+ │ │ └── feature_request.md
1121
+ │ ├── PULL_REQUEST_TEMPLATE.md
1122
+ │ └── CODEOWNERS
1123
+ ├── docs/
1124
+ │ ├── ARCHITECTURE.md
1125
+ │ ├── SECURITY.md # Politica de seguranca
1126
+ │ ├── CONTRIBUTING.md # Guia de contribuicao
1127
+ │ └── PROTOCOL.md # Especificacao do protocolo
1128
+ ├── LICENSE # MIT ou Apache-2.0
1129
+ ├── CHANGELOG.md # Historico de mudancas (semver)
1130
+ ├── CODE_OF_CONDUCT.md
1131
+ └── SECURITY.md # Como reportar vulnerabilidades
1132
+ ```
1133
+
1134
+ **Best practices**:
1135
+ - Semantic versioning (semver)
1136
+ - Conventional commits
1137
+ - CI/CD with GitHub Actions
1138
+ - Dependabot to update dependencies
1139
+ - CodeQL for static security analysis
1140
+ - Releases signed with GPG
1141
+ - Documentation on GitHub Pages
1142
+ - Badges in the README (CI, coverage, license, version)
1143
+ - Issue templates and PR templates
1144
+ - Security policy with a responsible-disclosure process
1145
+
1146
+ ### 11.7 Other Improvements
1147
+
1148
+ | Improvement | Priority | Complexity |
1149
+ |----------|------------|-------------|
1150
+ | Group messages with a group key | High | Medium |
1151
+ | ~~Encrypted file transfer~~ | ~~Medium~~ | ~~Medium~~ | ✅ Implemented |
1152
+ | ~~"Typing..." indicator~~ | ~~Low~~ | ~~Low~~ | ✅ Implemented |
1153
+ | ~~Sound notifications~~ | ~~Low~~ | ~~Low~~ | ✅ Implemented |
1154
+ | ~~Offline messages (server-side queue)~~ | ~~Medium~~ | ~~High~~ | ✅ Implemented |
1155
+ | Multiple devices per user | Low | High |
1156
+ | ~~Automatic key rotation~~ | ~~High~~ | ~~Medium~~ | ✅ Implemented |
1157
+ | ~~Message padding (anti-metadata)~~ | ~~Medium~~ | ~~Low~~ | ✅ Implemented |
1158
+ | ~~TLS on the WebSocket (wss://)~~ | ~~Medium~~ | ~~Low~~ | ✅ Implemented |
1159
+ | Server authentication (certificate) | Medium | Medium |
1160
+ | ~~Reconnect with encrypted state~~ | ~~High~~ | ~~High~~ | ✅ Implemented |
1161
+ | ~~P2P with mDNS (alternative mode)~~ | ~~Medium~~ | ~~High~~ | ✅ Implemented |
1162
+
1163
+ ---
1164
+
1165
+ ## 12. Glossary
1166
+
1167
+ | Term | Definition |
1168
+ |-------|-----------|
1169
+ | **E2EE** | End-to-End Encryption. Encryption where only the endpoints (sender and recipient) can read the content. Intermediaries (servers) have no access. |
1170
+ | **Curve25519** | An elliptic curve designed by Daniel J. Bernstein. Offers 128 bits of security with 32-byte keys. The basis of X25519 (key exchange). |
1171
+ | **X25519** | A key-exchange protocol (Diffie-Hellman) based on Curve25519. Two parties with different keys arrive at a shared secret. |
1172
+ | **XSalsa20** | A stream cipher. A variant of Salsa20 with an extended 24-byte nonce (vs. 8 in the original Salsa20). Designed by DJB. |
1173
+ | **Poly1305** | A Message Authentication Code (MAC). Generates a 16-byte tag proving the message was not altered. Combined with XSalsa20, it forms `crypto_box`. |
1174
+ | **AEAD** | Authenticated Encryption with Associated Data. A cipher that simultaneously guarantees confidentiality (nobody reads) and integrity (nobody alters). |
1175
+ | **Nonce** | Number Used Once. A unique value used in each encryption operation. If reused with the same key, security is compromised. |
1176
+ | **PFS** | Perfect Forward Secrecy. A property where the compromise of long-term keys does not affect past sessions. |
1177
+ | **DH** | Diffie-Hellman. A protocol that lets two parties establish a shared secret over an insecure channel. |
1178
+ | **MAC** | Message Authentication Code. A function that produces a verifiable tag guaranteeing a message's integrity and authenticity. |
1179
+ | **Fingerprint** | A short hash of a public key, used for human verification of identity. |
1180
+ | **Relay** | A server that forwards data without interpreting the content. |
1181
+ | **Handshake** | The process of establishing a secure connection between two parties, including key exchange and identity verification. |
1182
+ | **Side-channel attack** | An attack that exploits information leaked by the implementation (execution time, power consumption, cache) instead of attacking the algorithm mathematically. |
1183
+ | **sodium_malloc** | A libsodium function that allocates protected memory: does not go to swap, is zeroed when freed, and is protected against reads by other processes. |
1184
+ | **Double Ratchet** | An algorithm used by Signal that combines a DH ratchet with a KDF chain to guarantee per-message PFS. |
1185
+ | **TOFU** | Trust On First Use. A trust model where a peer's public key is accepted on the first connection and saved locally. Later changes trigger alerts (similar to SSH known_hosts). |
1186
+ | **SAS** | Short Authentication String. A short code (6 digits) derived from both sides' public keys, used for out-of-band identity verification. |
1187
+ | **BLAKE2b** | A fast and secure cryptographic hash function. Used as the KDF in the Double Ratchet and to compute the SAS. Supports keyed hashing (MAC). |
1188
+ | **mDNS** | Multicast DNS. A protocol for name resolution on local networks without a central DNS server. Used for service discovery (like Apple's Bonjour). |