Peer-to-peer style chat over a simple WebSocket relay with pairing, LAN access, and end‑to‑end encryption (X25519 + AES‑GCM). Includes an admin console on the server and a Python CLI client.
- Pairing: users list peers, invite, accept/reject, and chat in pairs
- LAN access: server binds
0.0.0.0:8765
- Admin console: list clients/pairs, kick, shutdown
- End‑to‑end encryption: X25519 key exchange, HKDF, AES‑GCM
- Short Authentication String: 6‑digit code to verify keys out‑of‑band
server.py
— entry; callsserver.main.run()
server/
— server implementationserver/main.py
— pairing, admin console, LAN bind, message relay
client.py
— entry; callsclient.main.run()
client/
— client implementationclient/main.py
— CLI, pairing, E2EE handshake, chatclient/crypto.py
— X25519 + HKDF + AES‑GCM helpers
client.html
— legacy browser client (not used by current flow)
- Python 3.9+
- Packages:
websockets
,cryptography
,textual
(for TUI)
Install dependencies:
pip install -r requirements.txt
python server.py --host 0.0.0.0 --port 8765
- Listens on
ws://0.0.0.0:8765
by default (LAN enabled). Ensure your firewall allows TCP 8765. - Use the admin console by typing commands into the server terminal:
help
— show commandsclients
— list all clients with status and addressavailable
— list available client IDspairs
— list active chat pairspending
— list outstanding inviteskick <id>
— disconnect a client by IDshutdown
— stop the server
TLS (WSS):
python server.py --certfile /path/cert.pem --keyfile /path/key.pem --port 443
You can also provide config/env:
- Config file (JSON) via
--config
orCHAT_CONFIG
with keyshost
,port
,certfile
,keyfile
,ping_interval
,ping_timeout
. - Env vars:
CHAT_HOST
,CHAT_PORT
,CHAT_CERTFILE
,CHAT_KEYFILE
,CHAT_PING_INTERVAL
,CHAT_PING_TIMEOUT
.
Start a client in another terminal (or on another LAN machine):
python client.py --server ws://<SERVER_IP>:8765
Replace <SERVER_IP>
with the server machine’s LAN IP (e.g. 192.168.1.50
).
Client commands:
/list
— list available peers/invite <id>
— invite a peer to chat/accept <id>
//reject <id>
— respond to an invite/leave
— end the current chat/trust
— trust current peer identity fingerprint (TOFU)/sendfile <path>
— send a file with E2EE (chunked)/sas
— show verification code to compare out‑of‑band/quit
— exit the client- Typing text sends an encrypted message once the secure session is established
Typical flow with two clients:
- Client A:
/list
→ see B’s ID - Client A:
/invite <B_ID>
- Client B:
/accept <A_ID>
- Both clients auto‑exchange keys. When you see “Secure session established”, chat is E2EE.
- Optionally run
/sas
on both ends and compare the 6‑digit code over another channel.
Reconnect & heartbeat:
- Client auto‑reconnects with exponential backoff, and sends WebSocket pings periodically.
TUI mode (optional):
python client.py --server ws://<SERVER_IP>:8765 --tui
Requires textual
. Shows logs and chat panes with an input box.
Launch a live dashboard for server stats and controls:
python server.py --tui --host 0.0.0.0 --port 8765
Panels:
- Summary: totals, available IDs, pending invites, active pairs
- Clients: table of clients with status, peer, pending, and address
- Command box: supports
kick <id>
,shutdown
,help
The non-TUI admin console still works when not using --tui
.
- Messages are end‑to‑end encrypted; the server only relays ciphertext.
- Without verifying the SAS code, a malicious relay could perform a MitM by substituting keys. Comparing codes mitigates this.
- For stronger binding, consider adding a shared passphrase or a PAKE in the future.
- TOFU: the client stores peer identity fingerprints under
.state/trusted.json
and warns on untrusted peers; use/trust
to add.
- The legacy
client.html
is kept for reference but not used by the current Python client/server flow. - File sizes are kept small via package scaffolding;
server.py
andclient.py
are thin entry points.