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.
- package/LICENSE +21 -0
- package/README.md +251 -0
- package/README.pt-BR.md +253 -0
- package/bin/ciphermesh.js +34 -0
- package/docs/ARCHITECTURE.md +1188 -0
- package/docs/SETUP.md +305 -0
- package/docs/demo.svg +46 -0
- package/package.json +87 -0
- package/src/client/ChatController.js +2476 -0
- package/src/client/Connection.js +129 -0
- package/src/client/FileTransfer.js +488 -0
- package/src/client/ImagePreview.js +88 -0
- package/src/client/UI.js +1830 -0
- package/src/client/index.js +231 -0
- package/src/crypto/CertPinStore.js +79 -0
- package/src/crypto/DeniableEncrypt.js +53 -0
- package/src/crypto/DoubleRatchet.js +574 -0
- package/src/crypto/Handshake.js +219 -0
- package/src/crypto/HistoryStore.js +241 -0
- package/src/crypto/IdentityBackup.js +70 -0
- package/src/crypto/KeyManager.js +134 -0
- package/src/crypto/MessageCrypto.js +181 -0
- package/src/crypto/NonceManager.js +72 -0
- package/src/crypto/SealedSender.js +58 -0
- package/src/crypto/SenderKey.js +204 -0
- package/src/crypto/StateManager.js +138 -0
- package/src/crypto/TrustStore.js +216 -0
- package/src/p2p/Discovery.js +80 -0
- package/src/p2p/P2PChatController.js +1856 -0
- package/src/p2p/PeerConnectionManager.js +252 -0
- package/src/p2p/PeerServer.js +68 -0
- package/src/p2p/index.js +219 -0
- package/src/protocol/messages.js +138 -0
- package/src/protocol/validators.js +175 -0
- package/src/server/CertManager.js +173 -0
- package/src/server/MessageRouter.js +80 -0
- package/src/server/OfflineQueue.js +124 -0
- package/src/server/SessionManager.js +296 -0
- package/src/server/WebSocketServer.js +632 -0
- package/src/server/index.js +89 -0
- package/src/shared/AuditLog.js +91 -0
- package/src/shared/PluginManager.js +83 -0
- package/src/shared/banner.js +271 -0
- package/src/shared/commandSuggest.js +59 -0
- package/src/shared/config.js +90 -0
- package/src/shared/constants.js +126 -0
- package/src/shared/coverTraffic.js +34 -0
- package/src/shared/dnd.js +60 -0
- package/src/shared/emoji.js +17 -0
- package/src/shared/fuzzy.js +40 -0
- package/src/shared/invite.js +61 -0
- package/src/shared/keyArt.js +66 -0
- package/src/shared/logger.js +38 -0
- package/src/shared/panic.js +38 -0
- package/src/shared/prompt.js +31 -0
- package/src/shared/terminalGraphics.js +72 -0
- package/src/shared/themes.js +36 -0
- package/src/shared/voiceNote.js +128 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Felipe Kreulich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
██████╗██╗██████╗ ██╗ ██╗███████╗██████╗ ███╗ ███╗███████╗███████╗██╗ ██╗
|
|
5
|
+
██╔════╝██║██╔══██╗██║ ██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔════╝██║ ██║
|
|
6
|
+
██║ ██║██████╔╝███████║█████╗ ██████╔╝██╔████╔██║█████╗ ███████╗███████║
|
|
7
|
+
██║ ██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║
|
|
8
|
+
╚██████╗██║██║ ██║ ██║███████╗██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║
|
|
9
|
+
╚═════╝╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### End-to-end encrypted terminal chat — the server can't read a single word.
|
|
13
|
+
|
|
14
|
+
[](https://github.com/FelipeKreulich/secret-chat-lan/actions/workflows/ci.yml)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
[](package.json)
|
|
17
|
+
[](docs/ARCHITECTURE.md)
|
|
18
|
+
|
|
19
|
+
**[🇧🇷 Leia em Português](README.pt-BR.md)** · [Setup Guide](docs/SETUP.md) · [Architecture](docs/ARCHITECTURE.md) · [Security Policy](SECURITY.md)
|
|
20
|
+
|
|
21
|
+
<img src="docs/demo.svg" alt="CipherMesh terminal demo" width="720">
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
You ──[encrypted payload]──▶ Relay (blind) ──[encrypted payload]──▶ Friend
|
|
29
|
+
Curve25519 + XSalsa20-Poly1305 · Double Ratchet · zero-knowledge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
CipherMesh is a terminal chat where **encryption is the product**. Keys live in
|
|
33
|
+
locked memory pages, every message gets a fresh ratchet key, and the relay
|
|
34
|
+
server only ever sees ciphertext — it can't read, alter, or fake anything.
|
|
35
|
+
Works on your LAN out of the box, and across the internet with
|
|
36
|
+
[Tailscale](docs/SETUP.md#connecting-over-the-internet-tailscale) (no port
|
|
37
|
+
forwarding, survives CGNAT).
|
|
38
|
+
|
|
39
|
+
## ✨ Highlights
|
|
40
|
+
|
|
41
|
+
| | Feature | The gist |
|
|
42
|
+
|-----|---------|----------|
|
|
43
|
+
| 🔐 | **Real E2EE** | Curve25519 + XSalsa20-Poly1305 via libsodium, keys in `sodium_malloc` — never touch disk |
|
|
44
|
+
| 🔄 | **Perfect Forward Secrecy** | Double Ratchet: one key per message, compromise today ≠ read yesterday |
|
|
45
|
+
| 🕶️ | **Metadata resistance** | Fixed-bucket length padding on every ciphertext + opt-in cover traffic (`/cover`) to blur when you chat |
|
|
46
|
+
| 🕵️ | **TOFU + SAS** | Key-change detection (MITM alarm) and 6-digit voice-verifiable codes |
|
|
47
|
+
| 🌐 | **LAN & internet** | Auto-detects Tailscale, shows the reachable address in the banner |
|
|
48
|
+
| 📨 | **Invites with QR** | `/invite` prints a `ciphermesh://` string + QR — paste it, you're in the right room |
|
|
49
|
+
| ✓✓ | **Encrypted read receipts** | The ✓✓ travels as ordinary ciphertext — the server can't tell it apart |
|
|
50
|
+
| 🗂️ | **Encrypted local history** | Opt-in (passphrase only), Argon2id + XSalsa20-Poly1305, `/search` & `/export` |
|
|
51
|
+
| 🖼️ | **Image previews** | Received photos render right in the chat as colored half-blocks |
|
|
52
|
+
| 📎 | **Resumable transfers** | Lost chunks are re-requested; reconnects resume from where they stopped |
|
|
53
|
+
| 💬 | **Modern chat feel** | Right-aligned own messages, per-user emoji avatars, replies with quotes, `:fire:` → 🔥 |
|
|
54
|
+
| 🎞️ | **Animated UI** | Splash intro, reconnect spinner, live transfer bars (shimmer + ETA), a lock-closing handshake on connect, and a pulsing "new messages ↓" pill |
|
|
55
|
+
| 👻 | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages *burn away* char-by-char when they expire |
|
|
56
|
+
| 🛰️ | **Serverless P2P mode** | mDNS peer discovery on the LAN — no relay at all |
|
|
57
|
+
|
|
58
|
+
## 🚀 Quick start
|
|
59
|
+
|
|
60
|
+
Run it without cloning (once published to npm):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx ciphermesh # client (default)
|
|
64
|
+
npx ciphermesh server # relay server
|
|
65
|
+
npx ciphermesh p2p # serverless P2P
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
macOS/Linux with Homebrew (see [`Formula/ciphermesh.rb`](Formula/ciphermesh.rb)):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
brew tap felipekreulich/ciphermesh
|
|
72
|
+
brew install ciphermesh
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or from source:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/FelipeKreulich/secret-chat-lan.git
|
|
79
|
+
cd secret-chat-lan
|
|
80
|
+
npm install
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Host** (one machine runs the relay):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm run server # or: docker compose up -d | npx ciphermesh server
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Prefer a prebuilt image? Pull the relay from GHCR (published on each release):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
docker run -p 3600:3600 ghcr.io/felipekreulich/secret-chat-lan:latest
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Everyone** (including the host):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm run client # nickname → passphrase (optional) → server address
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
On the same network, use the LAN IP from the server banner (`192.168.x.x:3600`).
|
|
102
|
+
Across the internet, install [Tailscale](https://tailscale.com) on both sides
|
|
103
|
+
and use the `Internet` address from the banner — full walkthrough in
|
|
104
|
+
[docs/SETUP.md](docs/SETUP.md).
|
|
105
|
+
|
|
106
|
+
Already in the chat? Run `/invite <your-ip>:3600` and send the string (or the
|
|
107
|
+
QR code) to whoever you want to pull in.
|
|
108
|
+
|
|
109
|
+
**No server at all?** `npm run p2p` — peers find each other via mDNS.
|
|
110
|
+
|
|
111
|
+
## 💬 Commands
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><b>Essentials</b></summary>
|
|
115
|
+
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---------|-------------|
|
|
118
|
+
| `/help` | All commands |
|
|
119
|
+
| `/users` | Who's online (with away/status) |
|
|
120
|
+
| `/msg <nick> <text>` | Private message (DM) |
|
|
121
|
+
| `/reply <text>` | Reply quoting the last received message |
|
|
122
|
+
| `/invite [host:port]` | Generate a `ciphermesh://` invite + QR code |
|
|
123
|
+
| `/nick <new>` | Change nickname (before joining — recovers from "nickname taken") |
|
|
124
|
+
| `/quit` | Leave |
|
|
125
|
+
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><b>Rooms</b></summary>
|
|
130
|
+
|
|
131
|
+
| Command | Description |
|
|
132
|
+
|---------|-------------|
|
|
133
|
+
| `/join <room>` | Enter/create a room |
|
|
134
|
+
| `/rooms` | List rooms |
|
|
135
|
+
| `/room` | Current room |
|
|
136
|
+
| `/owner` | Room owner |
|
|
137
|
+
| `/kick` `/mute` `/ban` | Owner moderation |
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary><b>Trust & security</b></summary>
|
|
143
|
+
|
|
144
|
+
| Command | Description |
|
|
145
|
+
|---------|-------------|
|
|
146
|
+
| `/fingerprint [nick]` | Key fingerprint + a deterministic **randomart** picture of the key |
|
|
147
|
+
| `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification |
|
|
148
|
+
| `/verify-confirm <nick>` | Mark peer as verified |
|
|
149
|
+
| `/trust <nick>` / `/trustlist` | Accept new key / trust status |
|
|
150
|
+
| `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
|
|
151
|
+
| `/deniable [on\|off]` | Plausible-deniability mode |
|
|
152
|
+
| `/panic [yes]` | Duress wipe — securely erase all on-disk secrets (session, history, trust, keys) and exit |
|
|
153
|
+
| `/cover [on\|constant\|off]` | Cover traffic — `on` = jittered decoys, `constant` = steady-rate paced channel |
|
|
154
|
+
| `/theme [name]` | Nick colour theme: neon, matrix, mono, sunset, ocean |
|
|
155
|
+
| `/ephemeral <30s\|5m\|1h\|off>` | Self-destructing messages |
|
|
156
|
+
| `/receipts [on\|off]` | Send read receipts (✓✓) |
|
|
157
|
+
| `/audit [n]` | Local audit log |
|
|
158
|
+
|
|
159
|
+
</details>
|
|
160
|
+
|
|
161
|
+
<details>
|
|
162
|
+
<summary><b>History & files</b></summary>
|
|
163
|
+
|
|
164
|
+
| Command | Description |
|
|
165
|
+
|---------|-------------|
|
|
166
|
+
| `/file <path>` | Offer a file (≤ 50MB) — the recipient must `/accept`; transfers resume |
|
|
167
|
+
| `/voice [secs]` | Record & send an encrypted voice note (needs `sox`/`ffmpeg`; default 10s) |
|
|
168
|
+
| `/play [path]` | Play the last received voice note (`afplay`/`sox`/`ffplay`) |
|
|
169
|
+
| `/accept [id]` / `/reject [id]` | Accept / decline an incoming file offer |
|
|
170
|
+
| `/img [path]` | Render the last received image in **full resolution** (kitty/iTerm2) |
|
|
171
|
+
| `/search <term>` | Search the encrypted local history |
|
|
172
|
+
| `/history [n]` | Last n messages from history |
|
|
173
|
+
| `/retention <7d\|24h\|30m>` | Purge local history older than the given age |
|
|
174
|
+
| `/export [path]` | Export history as .txt or .json (plaintext!) |
|
|
175
|
+
|
|
176
|
+
</details>
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary><b>Presence & fun</b></summary>
|
|
180
|
+
|
|
181
|
+
| Command | Description |
|
|
182
|
+
|---------|-------------|
|
|
183
|
+
| `/away [reason]` / `/back` | Mark yourself away |
|
|
184
|
+
| `/status <text\|off>` | Free-form status — emojis welcome (`/status :fire: coding`) |
|
|
185
|
+
| `/react <emoji>` | React to the last message |
|
|
186
|
+
| `/edit` `/delete` | Edit/delete your last message |
|
|
187
|
+
| `/pin` `/unpin` `/pins` | Pin messages |
|
|
188
|
+
| `/sound` `/notify` | Sound / desktop notifications |
|
|
189
|
+
| `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Do-not-disturb, mentions-only, or quiet hours |
|
|
190
|
+
| `/clear` | Clear the chat |
|
|
191
|
+
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
Typing `:fire:` anywhere becomes 🔥 (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Markdown works: \`code\`, **bold**, *italic*, links, plus fenced \`\`\` code blocks and | tables |. Received images preview inline (half-blocks) and render full-res with `/img` on kitty/iTerm2. Day separators and message grouping keep the log clean.
|
|
195
|
+
|
|
196
|
+
### Config file
|
|
197
|
+
|
|
198
|
+
Drop a `~/.ciphermesh/config.json` to set defaults and skip the prompts. All keys are optional (unknown keys are ignored):
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"nickname": "felipe",
|
|
203
|
+
"server": "wss://100.x.y.z:3600",
|
|
204
|
+
"sound": false,
|
|
205
|
+
"notify": true,
|
|
206
|
+
"receipts": true,
|
|
207
|
+
"deniable": false,
|
|
208
|
+
"cover": "constant",
|
|
209
|
+
"theme": "matrix",
|
|
210
|
+
"autoAway": 10,
|
|
211
|
+
"dnd": "22:00-08:00"
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`nickname`/`server` pre-fill the prompts (press Enter to accept); the rest are applied at startup as if you'd run the matching `/sound`, `/cover`, `/theme`, … command. Themes: `neon` (default), `matrix`, `mono`, `sunset`, `ocean`.
|
|
216
|
+
|
|
217
|
+
## 🔒 Security model
|
|
218
|
+
|
|
219
|
+
- The relay is **zero-knowledge**: it routes ciphertext and metadata-padded
|
|
220
|
+
payloads, nothing else. Read receipts, reactions, presence — all of it is
|
|
221
|
+
indistinguishable ciphertext to the server.
|
|
222
|
+
- **Traffic-analysis resistance**: every ciphertext is padded up to fixed
|
|
223
|
+
buckets so the relay can't read message length; file chunks are padded to a
|
|
224
|
+
uniform size so the exact file size doesn't leak either. `/cover on` adds
|
|
225
|
+
jittered decoy traffic and `/cover constant` paces your messages through a
|
|
226
|
+
steady-rate channel (decoys fill the idle slots) so the relay can't tell
|
|
227
|
+
active chatting from idle. Decoys are dropped silently by the receiver.
|
|
228
|
+
- **Anti-replay** via monotonic nonces, **key rotation** every hour with a
|
|
229
|
+
grace window, **secure memory wipe** (`sodium_memzero`) after use.
|
|
230
|
+
- **Duress wipe** (`/panic sim`): overwrites and deletes every on-disk secret
|
|
231
|
+
(session state, history, trust store, audit log), zeroes the in-memory keys,
|
|
232
|
+
and exits without saving — for a lost or seized device.
|
|
233
|
+
- Session state and local history are encrypted at rest with
|
|
234
|
+
**Argon2id + XSalsa20-Poly1305** — no passphrase, no persistence.
|
|
235
|
+
- Threat analysis and protocol details: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
236
|
+
Found something? See [SECURITY.md](SECURITY.md).
|
|
237
|
+
|
|
238
|
+
## 🧪 Development
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npm run server:dev # relay with auto-reload
|
|
242
|
+
npm test # 287 tests (crypto, ratchet, fuzz, controllers, transfers…)
|
|
243
|
+
npm run validate # lint + prettier + tests — what the CI runs
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
CI runs on every push/PR (Node 20 & 22). Tags `v*` trigger tests + a GitHub
|
|
247
|
+
Release automatically.
|
|
248
|
+
|
|
249
|
+
## 📄 License
|
|
250
|
+
|
|
251
|
+
[MIT](LICENSE) — do good things with it.
|
package/README.pt-BR.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
██████╗██╗██████╗ ██╗ ██╗███████╗██████╗ ███╗ ███╗███████╗███████╗██╗ ██╗
|
|
5
|
+
██╔════╝██║██╔══██╗██║ ██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔════╝██║ ██║
|
|
6
|
+
██║ ██║██████╔╝███████║█████╗ ██████╔╝██╔████╔██║█████╗ ███████╗███████║
|
|
7
|
+
██║ ██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║
|
|
8
|
+
╚██████╗██║██║ ██║ ██║███████╗██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║
|
|
9
|
+
╚═════╝╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Chat de terminal com criptografia ponta-a-ponta — o servidor não lê uma palavra.
|
|
13
|
+
|
|
14
|
+
[](https://github.com/FelipeKreulich/secret-chat-lan/actions/workflows/ci.yml)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
[](package.json)
|
|
17
|
+
[](docs/ARCHITECTURE.md)
|
|
18
|
+
|
|
19
|
+
**[🇺🇸 Read in English](README.md)** · [Guia de Setup](docs/SETUP.md) · [Arquitetura](docs/ARCHITECTURE.md) · [Política de Segurança](SECURITY.md)
|
|
20
|
+
|
|
21
|
+
<img src="docs/demo.svg" alt="Demo do CipherMesh no terminal" width="720">
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Você ──[payload cifrado]──▶ Relay (cego) ──[payload cifrado]──▶ Amigo
|
|
29
|
+
Curve25519 + XSalsa20-Poly1305 · Double Ratchet · zero-knowledge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
CipherMesh é um chat de terminal onde **a criptografia é o produto**. As chaves
|
|
33
|
+
vivem em páginas de memória travadas, cada mensagem usa uma chave nova do
|
|
34
|
+
ratchet, e o servidor de relay só enxerga ciphertext — não lê, não altera, não
|
|
35
|
+
forja nada. Funciona na sua LAN de cara, e pela internet com
|
|
36
|
+
[Tailscale](docs/SETUP.md#connecting-over-the-internet-tailscale) (sem port
|
|
37
|
+
forwarding, imune a CGNAT).
|
|
38
|
+
|
|
39
|
+
## ✨ Destaques
|
|
40
|
+
|
|
41
|
+
| | Feature | Resumo |
|
|
42
|
+
|-----|---------|--------|
|
|
43
|
+
| 🔐 | **E2EE de verdade** | Curve25519 + XSalsa20-Poly1305 via libsodium, chaves em `sodium_malloc` — nunca tocam o disco |
|
|
44
|
+
| 🔄 | **Perfect Forward Secrecy** | Double Ratchet: uma chave por mensagem — comprometer hoje ≠ ler ontem |
|
|
45
|
+
| 🕶️ | **Resistência a metadados** | Padding de comprimento em buckets fixos em todo ciphertext + cover traffic opcional (`/cover`) pra borrar quando você conversa |
|
|
46
|
+
| 🕵️ | **TOFU + SAS** | Alarme de troca de chave (MITM) e código de 6 dígitos verificável por voz |
|
|
47
|
+
| 🌐 | **LAN e internet** | Detecta Tailscale sozinho e mostra o endereço alcançável no banner |
|
|
48
|
+
| 📨 | **Convites com QR** | `/invite` gera uma string `ciphermesh://` + QR — colou, caiu na sala certa |
|
|
49
|
+
| ✓✓ | **Read receipts cifrados** | O ✓✓ viaja como ciphertext comum — o servidor não distingue de mensagem |
|
|
50
|
+
| 🗂️ | **Histórico local cifrado** | Opt-in (só com passphrase), Argon2id + XSalsa20-Poly1305, `/search` e `/export` |
|
|
51
|
+
| 🖼️ | **Preview de imagens** | Fotos recebidas renderizam no chat em half-blocks coloridos |
|
|
52
|
+
| 📎 | **Transferências com resume** | Chunks perdidos são re-pedidos; reconexão retoma de onde parou |
|
|
53
|
+
| 💬 | **Cara de app moderno** | Suas mensagens à direita, avatar de emoji por usuário, reply com citação, `:fire:` → 🔥 |
|
|
54
|
+
| 🎞️ | **Interface animada** | Splash na abertura, spinner de reconexão, barra de transferência viva (shimmer + ETA), cadeado fechando no handshake e um selo pulsante "novas mensagens ↓" |
|
|
55
|
+
| 👻 | **Deniable e efêmeras** | Modo de negação plausível (crypto simétrica); mensagens efêmeras *queimam* caractere a caractere ao expirar |
|
|
56
|
+
| 🛰️ | **Modo P2P sem servidor** | Descoberta de peers via mDNS na LAN — sem relay nenhum |
|
|
57
|
+
|
|
58
|
+
## 🚀 Começando
|
|
59
|
+
|
|
60
|
+
Rode sem clonar (depois de publicado no npm):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx ciphermesh # cliente (padrão)
|
|
64
|
+
npx ciphermesh server # servidor relay
|
|
65
|
+
npx ciphermesh p2p # P2P sem servidor
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
macOS/Linux com Homebrew (veja [`Formula/ciphermesh.rb`](Formula/ciphermesh.rb)):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
brew tap felipekreulich/ciphermesh
|
|
72
|
+
brew install ciphermesh
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Ou pelo código-fonte:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/FelipeKreulich/secret-chat-lan.git
|
|
79
|
+
cd secret-chat-lan
|
|
80
|
+
npm install
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Quem hospeda** (uma máquina roda o relay):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm run server # ou: docker compose up -d | npx ciphermesh server
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Prefere imagem pronta? Baixe o relay do GHCR (publicado a cada release):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
docker run -p 3600:3600 ghcr.io/felipekreulich/secret-chat-lan:latest
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Todo mundo** (incluindo quem hospeda):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm run client # nickname → passphrase (opcional) → endereço do servidor
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Na mesma rede, use o IP da LAN que aparece no banner do servidor
|
|
102
|
+
(`192.168.x.x:3600`). Pela internet, instalem [Tailscale](https://tailscale.com)
|
|
103
|
+
dos dois lados e usem o endereço `Internet` do banner — passo a passo completo
|
|
104
|
+
em [docs/SETUP.md](docs/SETUP.md).
|
|
105
|
+
|
|
106
|
+
Já está no chat? Rode `/invite <seu-ip>:3600` e mande a string (ou o QR) pra
|
|
107
|
+
quem você quiser puxar pra conversa.
|
|
108
|
+
|
|
109
|
+
**Sem servidor nenhum?** `npm run p2p` — os peers se encontram via mDNS.
|
|
110
|
+
|
|
111
|
+
## 💬 Comandos
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><b>Essenciais</b></summary>
|
|
115
|
+
|
|
116
|
+
| Comando | Descrição |
|
|
117
|
+
|---------|-----------|
|
|
118
|
+
| `/help` | Todos os comandos |
|
|
119
|
+
| `/users` | Quem está online (com away/status) |
|
|
120
|
+
| `/msg <nick> <texto>` | Mensagem privada (DM) |
|
|
121
|
+
| `/reply <texto>` | Responde citando a última mensagem recebida |
|
|
122
|
+
| `/invite [host:porta]` | Gera convite `ciphermesh://` + QR code |
|
|
123
|
+
| `/nick <novo>` | Troca de apelido (antes de entrar — recupera de "apelido em uso") |
|
|
124
|
+
| `/quit` | Sair |
|
|
125
|
+
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><b>Salas</b></summary>
|
|
130
|
+
|
|
131
|
+
| Comando | Descrição |
|
|
132
|
+
|---------|-----------|
|
|
133
|
+
| `/join <sala>` | Entra/cria uma sala |
|
|
134
|
+
| `/rooms` | Lista salas |
|
|
135
|
+
| `/room` | Sala atual |
|
|
136
|
+
| `/owner` | Dono da sala |
|
|
137
|
+
| `/kick` `/mute` `/ban` | Moderação (dono da sala) |
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary><b>Confiança & segurança</b></summary>
|
|
143
|
+
|
|
144
|
+
| Comando | Descrição |
|
|
145
|
+
|---------|-----------|
|
|
146
|
+
| `/fingerprint [nick]` | Fingerprint + um **randomart** determinístico da chave |
|
|
147
|
+
| `/verify <nick>` | Código SAS (~40 bits) + QR + randomart da chave para verificar |
|
|
148
|
+
| `/verify-confirm <nick>` | Marca o peer como verificado |
|
|
149
|
+
| `/backup [caminho]` | Backup cifrado da identidade + peers verificados (restaura no startup) |
|
|
150
|
+
| `/trust <nick>` / `/trustlist` | Aceita chave nova / status de confiança |
|
|
151
|
+
| `/deniable [on\|off]` | Modo de negação plausível |
|
|
152
|
+
| `/panic [sim]` | Wipe de coação — apaga com segurança todos os segredos do disco (sessão, histórico, confiança, chaves) e sai |
|
|
153
|
+
| `/cover [on\|constant\|off]` | Cover traffic — `on` = iscas com jitter, `constant` = canal de taxa constante |
|
|
154
|
+
| `/theme [nome]` | Tema de cores dos nicks: neon, matrix, mono, sunset, ocean |
|
|
155
|
+
| `/ephemeral <30s\|5m\|1h\|off>` | Mensagens autodestrutivas |
|
|
156
|
+
| `/receipts [on\|off]` | Envio de confirmação de leitura (✓✓) |
|
|
157
|
+
| `/audit [n]` | Log de auditoria local |
|
|
158
|
+
|
|
159
|
+
</details>
|
|
160
|
+
|
|
161
|
+
<details>
|
|
162
|
+
<summary><b>Histórico & arquivos</b></summary>
|
|
163
|
+
|
|
164
|
+
| Comando | Descrição |
|
|
165
|
+
|---------|-----------|
|
|
166
|
+
| `/file <caminho>` | Oferece arquivo (≤ 50MB) — o destinatário precisa dar `/accept`; retoma |
|
|
167
|
+
| `/voice [seg]` | Grava e envia nota de voz cifrada (precisa de `sox`/`ffmpeg`; default 10s) |
|
|
168
|
+
| `/play [caminho]` | Toca a última nota de voz recebida (`afplay`/`sox`/`ffplay`) |
|
|
169
|
+
| `/accept [id]` / `/reject [id]` | Aceita / recusa uma oferta de arquivo recebida |
|
|
170
|
+
| `/img [caminho]` | Renderiza a última imagem recebida em **alta resolução** (kitty/iTerm2) |
|
|
171
|
+
| `/retention <7d\|24h\|30m>` | Purga o histórico local mais antigo que o tempo dado |
|
|
172
|
+
| `/search <termo>` | Busca no histórico local cifrado |
|
|
173
|
+
| `/history [n]` | Últimas n mensagens do histórico |
|
|
174
|
+
| `/export [caminho]` | Exporta o histórico em .txt ou .json (texto plano!) |
|
|
175
|
+
|
|
176
|
+
</details>
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary><b>Presença & diversão</b></summary>
|
|
180
|
+
|
|
181
|
+
| Comando | Descrição |
|
|
182
|
+
|---------|-----------|
|
|
183
|
+
| `/away [motivo]` / `/back` | Marca/remove ausência |
|
|
184
|
+
| `/status <texto\|off>` | Status livre — emoji à vontade (`/status :fire: codando`) |
|
|
185
|
+
| `/react <emoji>` | Reage à última mensagem |
|
|
186
|
+
| `/edit` `/delete` | Edita/apaga sua última mensagem |
|
|
187
|
+
| `/pin` `/unpin` `/pins` | Fixa mensagens |
|
|
188
|
+
| `/sound` `/notify` | Notificações sonoras / desktop |
|
|
189
|
+
| `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Não perturbe, só menções, ou horário silencioso |
|
|
190
|
+
| `/clear` | Limpa o chat |
|
|
191
|
+
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
Digitar `:fire:` em qualquer lugar vira 🔥 (Tab autocompleta shortcodes).
|
|
195
|
+
**Ctrl+K** abre uma paleta de comandos fuzzy, **Ctrl+E** um seletor de emoji. PageUp/PageDown rolam o histórico. **Alt+Enter** (ou Shift+Enter onde o terminal suporta, além de Ctrl+J) insere uma nova linha para mensagens de várias linhas; Enter envia. Markdown funciona: \`código\`, **negrito**, *itálico*, links, além de blocos de código \`\`\` e | tabelas |. Imagens recebidas têm preview inline (half-blocks) e renderizam em alta resolução com `/img` no kitty/iTerm2. Separadores de dia e agrupamento de mensagens deixam o log limpo.
|
|
196
|
+
|
|
197
|
+
### Arquivo de config
|
|
198
|
+
|
|
199
|
+
Crie um `~/.ciphermesh/config.json` para definir padrões e pular os prompts. Todas as chaves são opcionais (chaves desconhecidas são ignoradas):
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"nickname": "felipe",
|
|
204
|
+
"server": "wss://100.x.y.z:3600",
|
|
205
|
+
"sound": false,
|
|
206
|
+
"notify": true,
|
|
207
|
+
"receipts": true,
|
|
208
|
+
"deniable": false,
|
|
209
|
+
"cover": "constant",
|
|
210
|
+
"theme": "matrix",
|
|
211
|
+
"autoAway": 10,
|
|
212
|
+
"dnd": "22:00-08:00"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`nickname`/`server` pré-preenchem os prompts (Enter aceita); o resto é aplicado na inicialização como se você tivesse rodado o comando `/sound`, `/cover`, `/theme`, … correspondente. Temas: `neon` (padrão), `matrix`, `mono`, `sunset`, `ocean`.
|
|
217
|
+
|
|
218
|
+
## 🔒 Modelo de segurança
|
|
219
|
+
|
|
220
|
+
- O relay é **zero-knowledge**: roteia ciphertext com padding anti-metadados e
|
|
221
|
+
nada mais. Read receipts, reações, presença — tudo é ciphertext
|
|
222
|
+
indistinguível pro servidor.
|
|
223
|
+
- **Resistência a análise de tráfego**: todo ciphertext é paddado até buckets
|
|
224
|
+
de tamanho fixo (o relay não lê o comprimento da mensagem); chunks de arquivo
|
|
225
|
+
são paddados a um tamanho uniforme, então o tamanho exato do arquivo também
|
|
226
|
+
não vaza. `/cover on` adiciona iscas com jitter e `/cover constant` faz suas
|
|
227
|
+
mensagens saírem por um canal de taxa constante (iscas preenchem os slots
|
|
228
|
+
ociosos), pra ele não distinguir conversa ativa de ociosa. Iscas são
|
|
229
|
+
descartadas em silêncio.
|
|
230
|
+
- **Anti-replay** com nonces monotônicos, **rotação de chaves** a cada hora com
|
|
231
|
+
janela de graça, **limpeza segura de memória** (`sodium_memzero`) após o uso.
|
|
232
|
+
- **Wipe de coação** (`/panic sim`): sobrescreve e apaga todos os segredos do
|
|
233
|
+
disco (estado da sessão, histórico, confiança, auditoria), zera as chaves em
|
|
234
|
+
memória e sai sem salvar — para um device perdido ou apreendido.
|
|
235
|
+
- Estado de sessão e histórico local são cifrados em repouso com
|
|
236
|
+
**Argon2id + XSalsa20-Poly1305** — sem passphrase, nada persiste.
|
|
237
|
+
- Análise de ameaças e detalhes do protocolo: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
238
|
+
Achou algo? Veja [SECURITY.md](SECURITY.md).
|
|
239
|
+
|
|
240
|
+
## 🧪 Desenvolvimento
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
npm run server:dev # relay com auto-reload
|
|
244
|
+
npm test # 287 testes (crypto, ratchet, fuzz, controllers, transferências…)
|
|
245
|
+
npm run validate # lint + prettier + testes — o mesmo que o CI roda
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
O CI roda em todo push/PR (Node 20 e 22). Tags `v*` disparam testes + GitHub
|
|
249
|
+
Release automaticamente.
|
|
250
|
+
|
|
251
|
+
## 📄 Licença
|
|
252
|
+
|
|
253
|
+
[MIT](LICENSE) — faça coisas boas com isso.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CipherMesh CLI entry point — dispatches to the client (default), the relay
|
|
3
|
+
// server, or serverless P2P mode. Enables `npx ciphermesh` after publishing.
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const TARGETS = {
|
|
7
|
+
client: '../src/client/index.js',
|
|
8
|
+
server: '../src/server/index.js',
|
|
9
|
+
p2p: '../src/p2p/index.js',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const HELP = `CipherMesh — end-to-end encrypted terminal chat
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
ciphermesh [client] connect to a relay server (default)
|
|
16
|
+
ciphermesh server run the zero-knowledge relay server
|
|
17
|
+
ciphermesh p2p serverless mode (mDNS peer discovery on the LAN)
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-h, --help show this help
|
|
21
|
+
-v, --version show the version
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const arg = process.argv[2];
|
|
25
|
+
|
|
26
|
+
if (arg === '-h' || arg === '--help' || arg === 'help') {
|
|
27
|
+
process.stdout.write(HELP);
|
|
28
|
+
} else if (arg === '-v' || arg === '--version') {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
30
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
31
|
+
} else {
|
|
32
|
+
const target = TARGETS[arg] || TARGETS.client;
|
|
33
|
+
await import(new URL(target, import.meta.url).href);
|
|
34
|
+
}
|