Add documentation: protocol spec, server admin, client guide
docs/PROTOCOL.md (520 lines): - Identity model (seed → Ed25519 + X25519 via HKDF) - X3DH key exchange (4 DH operations, ASCII flow diagram) - Double Ratchet (chain/DH ratchet, skipped keys, state machine) - KDF chains with domain separation strings - AEAD (ChaCha20-Poly1305) - Wire format (WireMessage enum, bincode serialization) - Pre-key bundle format and lifecycle docs/SERVER.md (429 lines): - Build and run instructions - Full API reference with request/response examples - Database structure (sled trees) - Deployment (nginx reverse proxy, systemd unit) - Security considerations - Backup and recovery docs/CLIENT.md (507 lines): - Quick start guide - All CLI commands with examples - Identity management and mnemonic backup - Web client usage and limitations - Session and pre-key management - Threat model table - Troubleshooting guide Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
507
warzone/docs/CLIENT.md
Normal file
507
warzone/docs/CLIENT.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# Warzone Client -- Operation Guide
|
||||
|
||||
---
|
||||
|
||||
## 1. Installation
|
||||
|
||||
### Build from Source
|
||||
|
||||
Requires Rust 1.75+.
|
||||
|
||||
```bash
|
||||
cd warzone/
|
||||
cargo build -p warzone-client --release
|
||||
```
|
||||
|
||||
The binary is at `target/release/warzone`. You can copy it anywhere or add
|
||||
`target/release` to your `PATH`.
|
||||
|
||||
```bash
|
||||
# Optional: install to ~/.cargo/bin
|
||||
cargo install --path crates/warzone-client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Generate a new identity
|
||||
warzone init
|
||||
|
||||
# 2. Register your key bundle with a server
|
||||
warzone register -s http://wz.example.com:7700
|
||||
|
||||
# 3. Send an encrypted message
|
||||
warzone send a3f8:c912:44be:7d01 "Hello from Warzone" -s http://wz.example.com:7700
|
||||
|
||||
# 4. Poll for incoming messages
|
||||
warzone recv -s http://wz.example.com:7700
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. CLI Commands
|
||||
|
||||
### warzone init
|
||||
|
||||
Generate a new identity (seed, keypair, and pre-keys).
|
||||
|
||||
```bash
|
||||
$ warzone init
|
||||
Identity generated!
|
||||
|
||||
Fingerprint: b7d1:e845:0022:9f3a
|
||||
|
||||
Recovery mnemonic (WRITE THIS DOWN):
|
||||
|
||||
1. abandon 2. ability 3. able 4. about
|
||||
5. above 6. absent 7. absorb 8. abstract
|
||||
9. absurd 10. abuse 11. access 12. accident
|
||||
13. account 14. accuse 15. achieve 16. acid
|
||||
17. acoustic 18. acquire 19. across 20. act
|
||||
21. action 22. actor 23. actress 24. actual
|
||||
|
||||
Seed saved to ~/.warzone/identity.seed
|
||||
Generated 1 signed pre-key + 10 one-time pre-keys
|
||||
|
||||
To register with a server, run:
|
||||
warzone send <recipient-fingerprint> <message> -s http://server:7700
|
||||
|
||||
Or register your key bundle manually:
|
||||
(bundle auto-registered on first send)
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Generates 32 random bytes (seed) from `OsRng`.
|
||||
2. Derives Ed25519 signing key and X25519 encryption key from the seed.
|
||||
3. Converts seed to a 24-word BIP39 mnemonic and displays it.
|
||||
4. Saves the raw seed to `~/.warzone/identity.seed` (mode 0600 on Unix).
|
||||
5. Generates 1 signed pre-key (id=1) and 10 one-time pre-keys (ids 0-9).
|
||||
6. Stores pre-key secrets in the local sled database at `~/.warzone/db/`.
|
||||
7. Saves the public pre-key bundle to `~/.warzone/bundle.bin`.
|
||||
|
||||
---
|
||||
|
||||
### warzone recover \<words...\>
|
||||
|
||||
Recover an identity from a BIP39 mnemonic.
|
||||
|
||||
```bash
|
||||
$ warzone recover abandon ability able about above absent absorb abstract \
|
||||
absurd abuse access accident account accuse achieve acid \
|
||||
acoustic acquire across act action actor actress actual
|
||||
Identity recovered!
|
||||
Fingerprint: b7d1:e845:0022:9f3a
|
||||
Seed saved to ~/.warzone/identity.seed
|
||||
```
|
||||
|
||||
**Note:** recovery restores the seed and keypair but does NOT restore
|
||||
pre-keys or sessions. You will need to run `warzone init`-style pre-key
|
||||
generation separately or your contacts will need to re-establish sessions.
|
||||
|
||||
---
|
||||
|
||||
### warzone info
|
||||
|
||||
Display your fingerprint and public keys.
|
||||
|
||||
```bash
|
||||
$ warzone info
|
||||
Fingerprint: b7d1:e845:0022:9f3a
|
||||
Signing key: 3a7c... (64 hex chars)
|
||||
Encryption key: 9d2f... (64 hex chars)
|
||||
```
|
||||
|
||||
Requires a saved identity (`~/.warzone/identity.seed`).
|
||||
|
||||
---
|
||||
|
||||
### warzone register
|
||||
|
||||
Register your pre-key bundle with a server.
|
||||
|
||||
```bash
|
||||
$ warzone register -s http://wz.example.com:7700
|
||||
Bundle registered with http://wz.example.com:7700
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
|
||||
| Flag | Short | Default | Description |
|
||||
|------|-------|---------|-------------|
|
||||
| `--server` | `-s` | `http://localhost:7700` | Server URL |
|
||||
|
||||
This uploads `~/.warzone/bundle.bin` to the server. Registration is also
|
||||
performed automatically on the first `send`.
|
||||
|
||||
---
|
||||
|
||||
### warzone send
|
||||
|
||||
Send an encrypted message to a recipient.
|
||||
|
||||
```bash
|
||||
$ warzone send a3f8:c912:44be:7d01 "Hello, are you safe?" -s http://wz.example.com:7700
|
||||
No existing session. Fetching key bundle for a3f8:c912:44be:7d01...
|
||||
Bundle registered with http://wz.example.com:7700
|
||||
Message sent to a3f8:c912:44be:7d01
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| `recipient` | Recipient fingerprint (e.g. `a3f8:c912:44be:7d01`) |
|
||||
| `message` | Message text (quote if it contains spaces) |
|
||||
|
||||
**Flags:**
|
||||
|
||||
| Flag | Short | Default | Description |
|
||||
|------|-------|---------|-------------|
|
||||
| `--server` | `-s` | `http://localhost:7700` | Server URL |
|
||||
|
||||
**Behavior:**
|
||||
1. Auto-registers your bundle with the server (if not already done).
|
||||
2. Checks for an existing Double Ratchet session with the recipient.
|
||||
3. If no session exists:
|
||||
- Fetches recipient's pre-key bundle from the server.
|
||||
- Verifies the signed pre-key signature.
|
||||
- Performs X3DH key exchange.
|
||||
- Initializes the Double Ratchet as Alice (initiator).
|
||||
- Sends a `WireMessage::KeyExchange` containing the X3DH parameters
|
||||
and the first encrypted message.
|
||||
4. If a session exists:
|
||||
- Encrypts using the existing ratchet.
|
||||
- Sends a `WireMessage::Message`.
|
||||
5. Updates the local session state.
|
||||
|
||||
---
|
||||
|
||||
### warzone recv
|
||||
|
||||
Poll for and decrypt incoming messages.
|
||||
|
||||
```bash
|
||||
$ warzone recv -s http://wz.example.com:7700
|
||||
Polling for messages as b7d1:e845:0022:9f3a...
|
||||
Received 2 message(s):
|
||||
|
||||
[new session] a3f8:c912:44be:7d01: Hello, are you safe?
|
||||
a3f8:c912:44be:7d01: I'm sending supplies tomorrow.
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
|
||||
| Flag | Short | Default | Description |
|
||||
|------|-------|---------|-------------|
|
||||
| `--server` | `-s` | `http://localhost:7700` | Server URL |
|
||||
|
||||
**Behavior:**
|
||||
1. Polls `/v1/messages/poll/{our_fingerprint}`.
|
||||
2. For each message:
|
||||
- Deserializes the `WireMessage` from bincode.
|
||||
- **KeyExchange:** loads signed pre-key secret and (if applicable)
|
||||
one-time pre-key secret from local storage, performs X3DH respond,
|
||||
initializes ratchet as Bob, decrypts the message, and saves the session.
|
||||
- **Message:** loads existing session, decrypts with the ratchet, saves
|
||||
updated session state.
|
||||
3. Prints decrypted messages to stdout.
|
||||
|
||||
**Note:** messages are currently NOT acknowledged after polling. They will
|
||||
be returned again on the next poll. Acknowledgment is TODO.
|
||||
|
||||
---
|
||||
|
||||
### warzone chat
|
||||
|
||||
Launch the interactive TUI.
|
||||
|
||||
```bash
|
||||
$ warzone chat -s http://wz.example.com:7700
|
||||
TODO: launch TUI connected to http://wz.example.com:7700
|
||||
```
|
||||
|
||||
**Status:** not yet implemented. The TUI will use `ratatui` and `crossterm`
|
||||
(dependencies are already in `Cargo.toml`). Planned for Phase 2.
|
||||
|
||||
---
|
||||
|
||||
## 4. Identity Management
|
||||
|
||||
### Storage Layout
|
||||
|
||||
```
|
||||
~/.warzone/
|
||||
identity.seed # 32-byte raw seed (plaintext -- encryption is TODO)
|
||||
bundle.bin # bincode-serialized PreKeyBundle (public data)
|
||||
db/ # sled database directory
|
||||
sessions/ # Double Ratchet state per peer
|
||||
pre_keys/ # signed and one-time pre-key secrets
|
||||
```
|
||||
|
||||
### File Permissions
|
||||
|
||||
On Unix, `identity.seed` is created with mode `0600` (owner read/write only).
|
||||
The sled database directory inherits default permissions.
|
||||
|
||||
### Seed Security
|
||||
|
||||
**Current state:** the seed is stored as **plaintext** 32 bytes. This is a
|
||||
known Phase 1 limitation.
|
||||
|
||||
**Planned (Phase 2):** encrypt the seed at rest using:
|
||||
- Passphrase input at startup
|
||||
- Argon2id key derivation from passphrase
|
||||
- ChaCha20-Poly1305 encryption of the seed bytes
|
||||
|
||||
### Mnemonic Backup
|
||||
|
||||
The 24-word BIP39 mnemonic shown during `init` is the ONLY way to recover
|
||||
your identity if you lose `~/.warzone/`. Write it down on paper and store it
|
||||
securely.
|
||||
|
||||
The mnemonic is displayed once at generation time and can be recovered from
|
||||
the seed using the protocol library, but the CLI does not currently expose a
|
||||
"show mnemonic" command.
|
||||
|
||||
### Recovery
|
||||
|
||||
```bash
|
||||
warzone recover word1 word2 word3 ... word24
|
||||
```
|
||||
|
||||
This recreates `~/.warzone/identity.seed` with the same seed. The same
|
||||
fingerprint and keypairs are derived deterministically. However:
|
||||
|
||||
- Pre-keys are NOT regenerated. Run `warzone init` on a fresh directory to
|
||||
generate new pre-keys (this will also generate a new seed, so you would need
|
||||
to coordinate).
|
||||
- Sessions are NOT recovered. All contacts will need to establish new sessions.
|
||||
|
||||
**TODO:** a `recover` flow that also regenerates pre-keys without creating a
|
||||
new seed.
|
||||
|
||||
---
|
||||
|
||||
## 5. Web Client
|
||||
|
||||
The web client is served by the server at `/`. Open it in a browser:
|
||||
|
||||
```
|
||||
http://localhost:7700/
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **Generate New Identity:** creates a random 32-byte seed in the browser.
|
||||
- **Recover from Mnemonic:** paste a hex-encoded seed (not BIP39 words;
|
||||
hex encoding is used as a placeholder).
|
||||
- **Chat interface:** dark-themed monospace UI with message display.
|
||||
- **Commands:**
|
||||
- `/help` -- show available commands
|
||||
- `/info` -- show your fingerprint
|
||||
- `/seed` -- display your seed (hex-encoded)
|
||||
|
||||
### How It Works
|
||||
|
||||
1. Seed is generated with `crypto.getRandomValues(32)`.
|
||||
2. ECDH P-256 keypair is derived (not X25519 -- Web Crypto limitation).
|
||||
3. Fingerprint is `SHA-256(ECDH_public_key)[0..16]` formatted as 4 hex
|
||||
groups.
|
||||
4. Seed is saved in `localStorage` under key `wz-seed`.
|
||||
5. On page load, the client tries to auto-load a saved seed.
|
||||
6. Public key is registered with the server via `POST /v1/keys/register`.
|
||||
7. Messages are polled every 5 seconds from `/v1/messages/poll/{fingerprint}`.
|
||||
|
||||
### Limitations
|
||||
|
||||
- **No cross-client compatibility:** the web client uses P-256 while the CLI
|
||||
uses X25519/Ed25519. Messages between the two cannot be decrypted. This
|
||||
will be resolved in Phase 2 (WASM port of the protocol library).
|
||||
- **No Double Ratchet:** message decryption is not implemented in JS.
|
||||
Received messages display as `[encrypted message]`.
|
||||
- **No BIP39:** seed is shown as hex bytes, not mnemonic words.
|
||||
- **Unencrypted seed storage:** `localStorage` is accessible to any JS on
|
||||
the same origin.
|
||||
|
||||
---
|
||||
|
||||
## 6. Session Management
|
||||
|
||||
### How Sessions Work
|
||||
|
||||
A "session" is a Double Ratchet state between you and one peer, identified
|
||||
by their fingerprint.
|
||||
|
||||
1. **First message to a peer:** X3DH key exchange establishes a shared secret.
|
||||
The ratchet is initialized. The session is saved in `~/.warzone/db/`
|
||||
under the `sessions` tree, keyed by the peer's fingerprint (hex-encoded).
|
||||
|
||||
2. **Subsequent messages:** the ratchet state is loaded, used to encrypt or
|
||||
decrypt, then saved back.
|
||||
|
||||
3. **Bidirectional:** both parties maintain the same session. When Bob
|
||||
receives Alice's KeyExchange, he initializes his side of the ratchet. From
|
||||
then on, both use `WireMessage::Message`.
|
||||
|
||||
### Session Storage
|
||||
|
||||
Sessions are serialized with `bincode` and stored in the `sessions` sled
|
||||
tree. The key is the peer's 32-character hex fingerprint.
|
||||
|
||||
### Session Reset
|
||||
|
||||
There is currently no command to reset a session. If a session becomes
|
||||
corrupted or out of sync:
|
||||
|
||||
1. Delete the local database: `rm -rf ~/.warzone/db/`
|
||||
2. Re-run `warzone init` to generate new pre-keys.
|
||||
3. Re-register with the server.
|
||||
4. Your contact must also reset their session with you.
|
||||
|
||||
**TODO (Phase 2):** a `warzone reset-session <fingerprint>` command.
|
||||
|
||||
---
|
||||
|
||||
## 7. Pre-Key Management
|
||||
|
||||
### What Are Pre-Keys
|
||||
|
||||
Pre-keys enable asynchronous session establishment. When Alice wants to
|
||||
message Bob for the first time:
|
||||
|
||||
1. Alice fetches Bob's **pre-key bundle** from the server.
|
||||
2. The bundle contains Bob's public identity key, a signed pre-key, and
|
||||
optionally a one-time pre-key.
|
||||
3. Alice uses these to perform X3DH without Bob being online.
|
||||
|
||||
### Pre-Key Types
|
||||
|
||||
| Type | Quantity | Lifetime | Purpose |
|
||||
|------|----------|----------|---------|
|
||||
| Signed pre-key | 1 (id=1) | Long-term (no rotation yet) | Medium-term DH key, signed by identity |
|
||||
| One-time pre-keys | 10 (ids 0-9) | Single use | Consumed during X3DH, then deleted |
|
||||
|
||||
### When to Replenish
|
||||
|
||||
One-time pre-keys are consumed when someone initiates a session with you.
|
||||
After all 10 are used, X3DH falls back to using only the signed pre-key
|
||||
(DH4 is skipped), which provides slightly weaker security properties.
|
||||
|
||||
**Current state:** there is no automatic replenishment. You must manually
|
||||
re-initialize if you expect many incoming new sessions.
|
||||
|
||||
**TODO (Phase 2):** the server will notify the client when one-time pre-key
|
||||
supply is low, and the client will upload fresh ones automatically.
|
||||
|
||||
---
|
||||
|
||||
## 8. Security Model
|
||||
|
||||
### What Is Encrypted
|
||||
|
||||
- **Message body:** encrypted with ChaCha20-Poly1305 using per-message keys
|
||||
from the Double Ratchet. Even the server cannot read it.
|
||||
|
||||
### What Is NOT Encrypted
|
||||
|
||||
- **Sender fingerprint:** visible to the server and anyone intercepting
|
||||
traffic.
|
||||
- **Recipient fingerprint:** visible to the server (needed for routing).
|
||||
- **Message size:** visible to the server.
|
||||
- **Timing:** when messages are sent and received.
|
||||
- **IP addresses:** visible to the server and network observers.
|
||||
- **Seed on disk:** stored as plaintext (encryption TODO).
|
||||
|
||||
### Threat Model
|
||||
|
||||
| Threat | Protected? | Notes |
|
||||
|--------|-----------|-------|
|
||||
| Server reads messages | Yes | E2E encryption; server sees only ciphertext |
|
||||
| Network eavesdropper reads messages | Yes | E2E encryption |
|
||||
| Server impersonates a user | Partially | Pre-key signatures prevent forgery of signed pre-keys, but the server could substitute a fake bundle (no key transparency yet) |
|
||||
| Compromised past session key | Yes | Forward secrecy via chain ratchet; break-in recovery via DH ratchet |
|
||||
| Stolen device (seed file) | No | Seed is plaintext on disk (encryption TODO) |
|
||||
| Metadata analysis (who talks to whom) | No | Fingerprints visible to server |
|
||||
| Active MITM on first contact | Partially | TOFU model; no out-of-band verification mechanism in the client yet |
|
||||
| One-time pre-keys exhausted | Graceful degradation | X3DH works without OT pre-keys but with reduced replay protection |
|
||||
|
||||
### Trust Model
|
||||
|
||||
**Trust on first use (TOFU):** the first time you message someone, you trust
|
||||
that the server returns their genuine pre-key bundle. There is no
|
||||
verification step yet.
|
||||
|
||||
**Planned (Phase 3):** DNS-based key transparency where users publish
|
||||
self-signed public keys in DNS TXT records, allowing cross-verification
|
||||
independent of the server.
|
||||
|
||||
---
|
||||
|
||||
## 9. Troubleshooting
|
||||
|
||||
### "No identity found. Run `warzone init` first."
|
||||
|
||||
You haven't generated an identity, or `~/.warzone/identity.seed` is missing.
|
||||
|
||||
```bash
|
||||
warzone init
|
||||
```
|
||||
|
||||
### "No bundle found. Run `warzone init` first."
|
||||
|
||||
The pre-key bundle file `~/.warzone/bundle.bin` is missing. This happens if
|
||||
you ran `recover` without a full `init`.
|
||||
|
||||
Re-run `warzone init` (this will generate a NEW identity). To keep your
|
||||
recovered identity, you would need to manually regenerate pre-keys (not yet
|
||||
supported as a standalone command).
|
||||
|
||||
### "failed to fetch recipient's bundle. Are they registered?"
|
||||
|
||||
The recipient has not registered their pre-key bundle with the server, or
|
||||
you are using the wrong server URL, or the fingerprint is incorrect.
|
||||
|
||||
- Verify the fingerprint (ask the recipient for theirs via `warzone info`).
|
||||
- Verify the server URL.
|
||||
- Ask the recipient to run `warzone register -s <server>`.
|
||||
|
||||
### "X3DH respond failed" / "missing signed pre-key"
|
||||
|
||||
Your signed pre-key secret is missing from the local database. This can
|
||||
happen if:
|
||||
- The database was deleted or corrupted.
|
||||
- You recovered an identity but did not regenerate pre-keys.
|
||||
|
||||
Fix: re-initialize with `warzone init` (generates a new identity) or restore
|
||||
from backup.
|
||||
|
||||
### "decrypt failed" / "no session"
|
||||
|
||||
- **"no session"**: you received a `WireMessage::Message` from someone you
|
||||
have no session with. This means you missed their initial `KeyExchange`
|
||||
message, or your session database was lost. Ask them to re-send their first
|
||||
message.
|
||||
- **"decrypt failed"**: the ratchet state is out of sync. This can happen if
|
||||
one side's state was lost or if messages were duplicated. Reset the session
|
||||
on both sides.
|
||||
|
||||
### Messages Keep Reappearing on recv
|
||||
|
||||
Messages are not auto-acknowledged after polling. This is a known Phase 1
|
||||
limitation. The same messages will be returned on every `recv` call.
|
||||
|
||||
**Workaround:** none currently. Acknowledgment will be added in Phase 2.
|
||||
|
||||
### Corrupted Database
|
||||
|
||||
If `~/.warzone/db/` is corrupted:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.warzone/db/
|
||||
warzone init # regenerate pre-keys (NOTE: generates a new identity)
|
||||
```
|
||||
|
||||
To keep your existing identity, manually copy `identity.seed` before
|
||||
deleting, then use `warzone recover` after re-init.
|
||||
Reference in New Issue
Block a user