# Warzone Messenger (featherChat) — Security Model & Threat Analysis **Version:** 0.0.20 **Last Updated:** 2026-03-28 --- ## Threat Model ### What Is Protected | Asset | Protection | |--------------------------|-----------------------------------------------| | Message content | E2E encrypted (ChaCha20-Poly1305 via Double Ratchet) | | Message integrity | AEAD authentication + Ed25519 signatures | | Past messages | Forward secrecy (Double Ratchet DH ratchet) | | Future messages | Future secrecy (recovery after DH ratchet step)| | Identity seed at rest | Argon2id + ChaCha20-Poly1305 passphrase encryption | | Identity portability | BIP39 mnemonic (24 words) | | Session state | Encrypted backup (HKDF + ChaCha20-Poly1305) | | Pre-key authenticity | Ed25519 signature on signed pre-keys | | Key exchange integrity | X3DH with 3-4 DH operations | ### What Is NOT Protected (Current) | Asset | Exposure | |--------------------------|------------------------------------------------| | Sender/recipient metadata| Server sees who talks to whom | | Message timing | Server sees when messages are sent/received | | Group membership | Server stores group member lists in plaintext | | Alias ↔ fingerprint mapping | Server stores this mapping | | Message sizes | Server sees encrypted message sizes | | Online/offline status | Server knows when clients connect via WebSocket| | IP addresses | Server sees client IP addresses | ### Trust Boundaries ``` ┌─────────────────────────────────────────────────────┐ │ TRUSTED: Client device │ │ - Seed in memory (after unlock) │ │ - Ratchet state │ │ - Plaintext messages (in memory and local DB) │ ├─────────────────────────────────────────────────────┤ │ SEMI-TRUSTED: Server │ │ - Sees metadata (who, when, how much) │ │ - Cannot read message content │ │ - Cannot forge messages (no signing keys) │ │ - Could drop, delay, or reorder messages │ │ - Could serve wrong pre-key bundles (MITM) │ │ → Mitigated by fingerprint verification │ │ → Future: DNS key transparency │ ├─────────────────────────────────────────────────────┤ │ UNTRUSTED: Network │ │ - All traffic encrypted (TLS + E2E) │ │ - Passive observer sees TLS-encrypted WebSocket │ │ - Active attacker cannot read or forge messages │ ├─────────────────────────────────────────────────────┤ │ UNTRUSTED: Other users │ │ - Cannot read messages not addressed to them │ │ - Cannot impersonate others (no access to seed) │ │ - Trust established via TOFU or fingerprint verify │ └─────────────────────────────────────────────────────┘ ``` --- ## Cryptographic Primitives ### Why Each Primitive Was Chosen | Primitive | Used For | Why This One | |---------------------|-------------------------|-------------------------------------------------| | Ed25519 | Signing, identity | Fast, compact (32-byte keys), no side-channel concerns, widely audited. Deterministic signatures (no nonce reuse risk). | | X25519 | Key exchange (DH) | Paired with Ed25519, same curve family. Constant-time implementations. Used by Signal, WireGuard, Noise. | | ChaCha20-Poly1305 | AEAD encryption | Stream cipher — no padding oracle attacks. Faster than AES on platforms without AES-NI (ARM, WASM). Used by TLS 1.3, WireGuard, Signal. | | HKDF-SHA256 | Key derivation | Standard (RFC 5869). Clean domain separation via info strings. Used throughout Signal protocol. | | SHA-256 | Fingerprints | Ubiquitous, well-understood. 128-bit truncation for fingerprints provides sufficient collision resistance for this use case. | | Argon2id | Passphrase KDF | Winner of the Password Hashing Competition. Memory-hard (resists GPU/ASIC attacks). `id` variant provides resistance to both side-channel and GPU attacks. | | secp256k1 ECDSA | Ethereum compatibility | Required for Ethereum address derivation and wallet interop. Not used for messaging crypto. | | Keccak-256 | Ethereum addresses | Required by Ethereum spec. Only used for address derivation, not for messaging. | ### Crate Security All crypto crates are widely audited Rust implementations: | Crate | Notes | |-----------------------|------------------------------------------------| | `ed25519-dalek` 2.x | Maintained by dalek-cryptography team | | `x25519-dalek` 2.x | Same team, constant-time DH | | `chacha20poly1305` 0.10| RustCrypto project, audited | | `hkdf` 0.12 | RustCrypto project | | `sha2` 0.10 | RustCrypto project | | `argon2` 0.5 | RustCrypto project, Argon2id support | | `k256` 0.13 | RustCrypto secp256k1 implementation | | `rand` 0.8 | OS-provided randomness (OsRng) | | `zeroize` 1.x | Secure memory zeroing (Seed, keys) | --- ## Key Derivation Paths All keys are derived from a single 32-byte seed using HKDF-SHA256 with distinct info strings for domain separation: ``` Seed (32 bytes) │ ├─ HKDF(ikm=seed, salt="", info="warzone-ed25519") → Ed25519 signing key │ ├─ HKDF(ikm=seed, salt="", info="warzone-x25519") → X25519 encryption key │ ├─ HKDF(ikm=seed, salt="", info="warzone-secp256k1") → secp256k1 key (Ethereum) │ └─ HKDF(ikm=seed, salt="", info="warzone-history") → History encryption key ``` ### X3DH Key Derivation ``` DH1 = our_identity_x25519 * their_signed_pre_key DH2 = our_ephemeral * their_identity_x25519 DH3 = our_ephemeral * their_signed_pre_key DH4 = our_ephemeral * their_one_time_pre_key (optional) shared_secret = HKDF(ikm=DH1||DH2||DH3[||DH4], salt="", info="warzone-x3dh") ``` ### Double Ratchet Key Derivation ``` Root KDF: HKDF(ikm=DH_output, salt=root_key, info="warzone-rk") → (new_root_key, chain_key) Chain KDF: HKDF(ikm=chain_key, salt="", info="warzone-ck") → (new_chain_key, message_key) ``` ### Sender Key Derivation ``` Message key: HKDF(ikm=chain_key, salt="", info="wz-sk-msg-{generation}-{counter}") Chain step: HKDF(ikm=chain_key, salt="", info="wz-sk-chain") ``` ### Seed Encryption ``` salt = random(16 bytes) key = Argon2id(passphrase, salt) → 32 bytes nonce = random(12 bytes) ciphertext = ChaCha20-Poly1305(key, nonce, seed) File: WZS1(4) || salt(16) || nonce(12) || ciphertext(48) ``` --- ## Forward Secrecy ### What Forward Secrecy Means If an attacker compromises your current keys, they **cannot decrypt past messages**. Each message uses a unique key derived from the ratchet state, and old keys are deleted after use. ### How the Double Ratchet Provides It ``` Message 1: chain_key_0 → message_key_0 (used, then deleted) Message 2: chain_key_1 → message_key_1 (used, then deleted) ... DH Ratchet step: new DH exchange → new root_key → new chain Message N: chain_key_N → message_key_N (used, then deleted) ``` **Symmetric ratchet:** Each message key is derived from the previous chain key, then the old chain key is overwritten. Compromise of the current chain key reveals future messages in this chain, but not past ones. **DH ratchet:** Periodically, a new Diffie-Hellman exchange occurs (new ephemeral keypair). This "heals" from compromise — even if the current chain key is stolen, after the next DH ratchet step, the attacker is locked out again. ### Forward Secrecy Properties | Scenario | Past Messages | Future Messages | |---------------------------------------|---------------|-----------------| | Current message key compromised | Safe | Safe | | Current chain key compromised | Safe | At risk until next DH ratchet | | DH private key compromised | Safe | At risk until next DH ratchet | | Seed compromised | At risk (all) | At risk (all) | **Key insight:** Compromising the seed compromises everything because all keys derive from it. Protect the seed above all else. ### Skipped Message Keys The Double Ratchet caches up to 1,000 skipped message keys to handle out-of-order delivery. These cached keys are a temporary window — they allow decryption of delayed messages but represent stored key material. --- ## Sender Keys: Trade-offs for Groups ### Efficiency vs Forward Secrecy **Double Ratchet (1:1):** O(1) encrypt, O(1) decrypt. Perfect forward secrecy per message. **Sender Keys (groups):** O(1) encrypt (one ciphertext for all members), O(1) decrypt per member. Forward secrecy per sender key chain, but NOT per-message. ### What Sender Keys Provide - Forward ratcheting: each message advances the chain. Once a message key is derived and used, the chain key moves forward irreversibly. - Compromise of the current chain key reveals future messages in this generation, but not past messages. - Key rotation on member join/leave: all members generate new sender keys. ### What Sender Keys Do NOT Provide - **No per-message forward secrecy:** Unlike Double Ratchet, there is no DH ratchet step per message. The symmetric chain ratchets forward, but a compromised chain key reveals all future messages until rotation. - **No future secrecy from member removal:** When a member is kicked, all remaining members must rotate their sender keys. If the kicked member cached the old chain key before rotation, they can read messages encrypted before the rotation completes. ### When This Matters For groups under 50 members (the target), Sender Keys are a reasonable trade-off. For larger or higher-security groups, MLS (Message Layer Security, RFC 9420) would be more appropriate. MLS is not implemented and is not planned for the near term. --- ## Seed Security ### Encryption at Rest The seed file uses defense-in-depth: 1. **Argon2id** with default parameters for memory-hard key derivation 2. **ChaCha20-Poly1305** for authenticated encryption 3. **Random salt** (16 bytes) prevents rainbow tables 4. **Random nonce** (12 bytes) prevents nonce reuse 5. **File permissions** set to 0600 on Unix ### Passphrase Strength Argon2id makes brute-force expensive, but the passphrase is still the weak link. Users should choose strong passphrases. An empty passphrase stores the seed in plaintext (for testing only). ### Memory Safety The `Seed` struct implements `Zeroize` and `ZeroizeOnDrop` from the `zeroize` crate. When the seed goes out of scope, its memory is securely overwritten with zeros. ### Hardware Wallet Concept (future) The ideal: seed never leaves a hardware device. Ed25519/X25519 operations delegated to Ledger/Trezor. Session key delegation allows daily use without touching the hardware wallet for every message. --- ## Server Trust Model ### What the Server Can Do - **See metadata:** sender fingerprint, recipient fingerprint, timestamp, message size - **Drop messages:** silently discard messages (denial of service) - **Delay messages:** hold messages before delivery - **Reorder messages:** deliver messages in a different order - **Serve wrong pre-key bundles:** MITM attack on key exchange (mitigated by fingerprint verification) - **See group membership:** group member lists are stored in plaintext - **See online status:** WebSocket connections reveal when users are online - **See alias mappings:** alias ↔ fingerprint relationships ### What the Server CANNOT Do - **Read message content:** all messages are E2E encrypted - **Forge messages:** the server does not have users' signing keys - **Derive keys:** the server never has seeds, private keys, or session keys - **Decrypt past messages:** even if the server is later compromised, stored ciphertext remains unreadable - **Modify messages undetected:** AEAD authentication detects tampering ### MITM via Pre-Key Substitution The most serious server attack: serve a malicious pre-key bundle (the server's own) instead of the recipient's real bundle. The sender would unknowingly encrypt to the server. **Current mitigation:** Users verify fingerprints out-of-band. If the fingerprint matches, the pre-key bundle is authentic (because the signed pre-key is signed by the identity key corresponding to the fingerprint). **Future mitigation:** DNS key transparency — users publish their public keys in DNS TXT records with self-signatures. The server cannot forge these records without the user's private key. --- ## Alias System Security ### TTL and Reclamation - Aliases expire after **365 days** of inactivity - **30-day grace period** after expiry: only the recovery key holder can reclaim - After grace period: anyone can register the alias - Activity auto-renews: sending messages, WebSocket activity This prevents: - **Squatting:** unused aliases are reclaimed - **Immediate takeover:** grace period gives the original owner time to recover ### Recovery Keys - Generated server-side (16 random bytes, displayed as 32 hex chars) - Rotated on each recovery operation - Stored alongside the alias record - The only way to reclaim an alias after losing access to the associated fingerprint ### Admin Capabilities The server admin (via `WARZONE_ADMIN_PASSWORD`) can: - Remove any alias (`/alias/admin-remove`) - Access is password-protected but not cryptographically authenticated The admin **cannot**: - Read messages - Impersonate users - Modify pre-key bundles without detection --- ## WASM Security ### Same Crypto as Native The web client uses the exact same Rust code compiled to WebAssembly: - `warzone-protocol` compiled via `wasm-pack` - X3DH, Double Ratchet, ChaCha20-Poly1305 — identical implementations - CLI-to-web and web-to-CLI messages are fully interoperable ### Randomness WASM uses `OsRng` from the `rand` crate, which maps to `crypto.getRandomValues()` in the browser. This is a cryptographically secure random number generator provided by the Web Crypto API. ### Key Storage Limitations | Concern | Native CLI | Web Client | |----------------------|-------------------------------|------------------------------| | Seed storage | Encrypted file (Argon2id) | localStorage (plaintext hex) | | Session persistence | sled DB on disk | localStorage (base64) | | Memory protection | Zeroize on drop | WASM linear memory (no zeroize guarantee) | | Process isolation | OS process isolation | Browser sandbox | | Side channels | Constant-time crypto | Same (WASM), but JS interop may leak timing | **Key risk:** The web client stores the seed as plaintext hex in `localStorage`. Any XSS vulnerability could steal the seed. The native client encrypts the seed with a passphrase. ### No OTPKs in Web Client The web client does not generate one-time pre-keys because `localStorage` cannot reliably store secrets that must be used exactly once. X3DH works without DH4 (the one-time pre-key DH). This means: - The key exchange still produces a shared secret from 3 DH operations - Anti-replay protection is slightly weaker (an attacker who captures the initial key exchange message could replay it if the server does not enforce OTP key deletion) - The server-side dedup tracker provides partial replay protection --- ## Known Weaknesses and Mitigations Planned ### 1. No Sealed Sender **Weakness:** The server sees sender and recipient fingerprints. **Mitigation (Phase 6):** Sealed sender — the server only sees the recipient, not the sender. The sender's identity is encrypted inside the E2E envelope. ### 2. No Traffic Analysis Protection **Weakness:** Message timing and sizes reveal communication patterns. **Mitigation (Phase 6):** Traffic padding and shaping. Optional onion routing between federated servers. ### 3. Web Client Seed Exposure **Weakness:** Seed stored as plaintext hex in `localStorage`. **Mitigation (planned):** Use Web Crypto API's `CryptoKey` with `non-extractable` flag. Derive encryption keys inside a Service Worker. Prompt for passphrase on load. ### 4. No Key Transparency **Weakness:** Server could serve malicious pre-key bundles (MITM). **Mitigation (Phase 3):** DNS TXT records with self-signed public keys. Cross-check server response against DNS. ### 5. Server Metadata Exposure **Weakness:** Group membership, alias mappings, online status visible to server. **Mitigation (partial, Phase 6):** Sealed sender hides sender identity. Group membership could be encrypted to group members only. ### 6. No Post-Quantum Crypto **Weakness:** All key exchanges use classical DH (X25519). A future quantum computer could break stored ciphertext. **Mitigation (future):** Hybrid key exchange (X25519 + ML-KEM/Kyber). The HKDF construction supports this naturally — concatenate classical and post-quantum shared secrets as HKDF input. ### 7. Auth Token Storage **Weakness:** Challenge nonces stored in memory (not persisted). Server restart clears pending challenges. **Mitigation (planned):** Store challenges in sled DB with TTL. --- ## Comparison with Other Messengers ### vs Signal | Aspect | Warzone | Signal | |-----------------------|----------------------------------|----------------------------------| | Protocol | X3DH + Double Ratchet (same) | X3DH + Double Ratchet | | Groups | Sender Keys | Sender Keys (+ considering MLS) | | Identity | Seed-based (BIP39 mnemonic) | Phone number based | | Server | Self-hosted, open source | Centralized | | Federation | Planned (DNS-based) | No federation | | Offline delivery | Mule protocol (planned) | Push notification only | | Metadata protection | Not yet (planned) | Sealed sender, SGX enclaves | | Audit | Not audited | Extensively audited | | Mobile | Not yet (planned) | Native iOS/Android apps | | Phone requirement | No phone needed | Requires phone number | ### vs Matrix (Element) | Aspect | Warzone | Matrix/Element | |-----------------------|----------------------------------|----------------------------------| | Protocol | Signal protocol (X3DH + DR) | Olm/Megolm (Double Ratchet variant) | | Groups | Sender Keys | Megolm (similar concept) | | Federation | Planned (DNS TXT) | Built-in (homeserver federation) | | Complexity | Minimal (5 crates) | Complex (homeserver, synapse) | | Encryption by default | Always E2E | Optional (not all rooms) | | Binary size | Single static binary | Python/Node server + Electron client | | Offline/mule | Designed for it | Not designed for offline | ### vs SimpleX | Aspect | Warzone | SimpleX | |-----------------------|----------------------------------|----------------------------------| | Identity | Seed-based fingerprint | No user identity (contact-level) | | Metadata | Server sees sender/recipient | Server sees neither (relays) | | Groups | Sender Keys | Per-member encryption | | Federation | Planned (DNS) | Relay-based | | Offline delivery | Mule protocol (planned) | Queue-based | | Simplicity | Single binary, sled DB | Haskell server, complex setup | | Ethereum integration | Built-in | None | ### Key Differentiators 1. **Seed-based identity** with BIP39 mnemonic — no phone numbers, no accounts, portable across devices 2. **Dual-curve identity** — same seed produces both messaging keys and Ethereum address 3. **Designed for warzone conditions** — mule protocol, transport abstraction, offline-first 4. **Single static binary** — no runtime dependencies, easy deployment 5. **WASM web client** with identical crypto — no JS crypto, same Rust code 6. **Self-hosted by design** — no dependency on centralized infrastructure