Security audit preparation: - Threat model with STRIDE analysis and 5 threat actors - Crypto boundaries documenting all 11 primitives and key lifecycle - Audit scope document for external security firms
8.3 KiB
Security Audit Scope
Project Summary
quicprochat is a production-grade end-to-end encrypted group messenger implemented in Rust. It uses MLS (RFC 9420) for group key agreement with a hybrid post-quantum KEM (X25519 + ML-KEM-768), OPAQUE for password-authenticated key exchange, and QUIC + TLS 1.3 as the transport layer. The project comprises approximately 38,000 lines of Rust across 9 workspace crates, with 300+ tests passing.
Scope
Primary Scope (Critical)
These components handle all cryptographic operations and should receive the deepest scrutiny.
quicprochat-core -- All Cryptographic Primitives
| File | Lines | Responsibility |
|---|---|---|
src/hybrid_kem.rs |
~630 | Hybrid X25519 + ML-KEM-768 KEM: key generation, encrypt/decrypt, encapsulate/decapsulate, HKDF key derivation |
src/hybrid_crypto.rs |
~540 | OpenMLS OpenMlsCrypto + OpenMlsProvider implementation with hybrid HPKE routing |
src/identity.rs |
~250 | Ed25519 identity keypair: generation, signing, verification, zeroization |
src/group.rs |
~1080 | MLS group state machine: create, join, add/remove members, send/receive, epoch management |
src/keypackage.rs |
~100 | MLS KeyPackage generation with hybrid init keys |
src/keystore.rs |
~710 | Disk-backed OpenMLS key store with file permission restrictions |
src/sealed_sender.rs |
~160 | Sender identity + Ed25519 signature envelope inside MLS payload |
src/pq_noise.rs |
~690 | Post-quantum Noise_XX handshake with ML-KEM-768 mixing |
src/opaque_auth.rs |
~20 | OPAQUE cipher suite definition (Ristretto255, Triple-DH, Argon2id) |
src/recovery.rs |
~340 | Recovery code generation, Argon2id key derivation, encrypted backup bundles |
src/padding.rs |
~270 | Message padding to fixed buckets + uniform boundary padding |
src/safety_numbers.rs |
~80 | Signal-style safety number computation |
src/transcript.rs |
~400 | Encrypted hash-chained transcript archive |
quicprochat-server/src/domain/auth.rs -- OPAQUE Server Logic
| File | Lines | Responsibility |
|---|---|---|
src/domain/auth.rs |
~170 | OPAQUE registration (start/finish), session token validation, expiry cleanup |
Secondary Scope (Important)
These components handle transport security and trust anchors. They are lower risk than the primary scope but still security-relevant.
quicprochat-rpc -- RPC Transport Security
| File | Lines | Responsibility |
|---|---|---|
src/framing.rs |
~200 | Wire format encoding/decoding, payload size limits (4 MiB max) |
src/auth_handshake.rs |
~80 | Session token exchange over QUIC bi-stream |
src/server.rs |
~300 | QUIC server setup, TLS configuration, connection handling |
src/middleware.rs |
~200 | Tower middleware: rate limiting, timeouts |
quicprochat-kt -- Key Transparency
| File | Lines | Responsibility |
|---|---|---|
src/lib.rs |
~65 | Merkle log hash functions (leaf_hash, node_hash) with RFC 6962 domain separation |
src/tree.rs |
~150 | Append-only Merkle tree (insert, root computation) |
src/proof.rs |
~100 | Inclusion proof generation and verification |
src/revocation.rs |
~100 | Key revocation log with reason codes |
quicprochat-server -- Security-Relevant Server Components
| File | Lines | Responsibility |
|---|---|---|
src/domain/rate_limit.rs |
~150 | Per-client rate limiting |
src/domain/traffic_resistance.rs |
~100 | Decoy traffic generation, timing jitter, payload padding |
src/domain/moderation.rs |
~100 | Abuse prevention (report handling) |
src/tls.rs |
~100 | TLS certificate loading and configuration |
Out of Scope
The following components are explicitly excluded from the audit:
- Client SDKs:
quicprochat-sdk, Go/Python/TypeScript/WASM/FFI bindings (thin wrappers) - CLI/TUI client:
quicprochat-client(user interface, no crypto logic) - Plugin API:
quicprochat-plugin-api(#![no_std]C-ABI interface, no crypto) - P2P networking:
quicprochat-p2p(iroh integration, feature-gated) - Proto definitions:
quicprochat-proto(generated code, no security logic) - Documentation: mdBook sources, README, ROADMAP
- CI/CD: GitHub Actions, Dockerfiles, justfile
- Non-crypto server domain: user management, group metadata, blob storage, delivery routing
Specific Questions for Auditors
Hybrid KEM Construction
- Is the hybrid combiner
HKDF-SHA256(salt, X25519_ss || ML-KEM_ss, info)a sound dual-PRF construction? Does it provide IND-CCA2 security assuming either component is secure? - Is the nonce handling correct? The hybrid KEM uses random 12-byte nonces (not derived from HKDF). Is there a nonce collision risk at the expected message volume?
- Is the
derive_from_ikm()construction (HKDF -> seeded StdRng -> key generation) suitable for deterministic key derivation in the MLS HPKE key schedule?
OpenMLS Integration
- Does the
HybridCryptoProvidercorrectly satisfy theOpenMlsProvidertrait contract? Are there edge cases where hybrid key detection by length could fail or be spoofed? - Is the
DiskKeyStoreimplementation (wrappingMemoryStoragewith disk flush) safe for concurrent access? Could a crash betweenMemoryStorageupdate and disk flush cause key loss? - Is the MLS group lifecycle (create, add_member with merge_pending_commit, join_group via StagedWelcome) correctly implemented? Are there state consistency issues after failed operations?
Timing Side-Channels
- Are there timing side-channels in the OPAQUE registration/login flow? The
opaque-kecrate uses Ristretto255 (constant-time), but is the surrounding code (deserialization, error handling) timing-safe? - Is
constant_time_eq()inrecovery.rscorrectly implemented? The early return on length mismatch is intentional (lengths are not secret), but verify the XOR-accumulate loop.
Noise_XX + ML-KEM Layering
- Is the PQ Noise handshake (
pq_noise.rs) sound? Specifically:- Is ML-KEM ciphertext placement in message 2 correct (after
eeDH, beforeseDH)? - Is
mix_key(mlkem_ss)the right integration point for the post-quantum shared secret? - Does ML-KEM implicit rejection (pseudorandom wrong shared secret) cause any subtle failures beyond AEAD decryption error?
- Is ML-KEM ciphertext placement in message 2 correct (after
Zeroization
- Is zeroization complete? Specifically:
x25519_dalek::StaticSecretdoes not publicly implementZeroize. Is theHybridKeypairstruct'sx25519_skfield securely erased on drop?- Are there intermediate buffers or stack copies of secret material that escape
Zeroizingwrappers? - Does the
DiskKeyStoreflush-on-write pattern leave secret material in OS page cache or filesystem buffers?
Key Transparency
- Is the Merkle log construction (RFC 6962-style domain separation with
0x00/0x01prefixes) resistant to second-preimage attacks? - Could an adversarial server forge inclusion proofs for non-existent entries?
Access
- Repository:
git clone https://github.com/quicprochat/quicprochat - Build:
cargo build --workspace(Rust 1.75+, no system dependencies --protobuf-srcvendors protoc) - Test:
cargo test --workspace(300+ tests, runs in ~60s) - Lint:
cargo clippy --workspace -- -D warnings - Documentation:
cd docs && mdbook build
Key Entry Points
- Crypto primitives:
crates/quicprochat-core/src/ - Server auth:
crates/quicprochat-server/src/domain/auth.rs - RPC framing:
crates/quicprochat-rpc/src/framing.rs - Key transparency:
crates/quicprochat-kt/src/
Timeline and Budget
Based on the scope (approximately 5,000 lines of security-critical Rust code in primary scope, plus ~1,000 lines in secondary scope), we recommend:
- Duration: 4-6 weeks (2 auditors)
- Budget range: $80,000 - $150,000
- Firm type: Specialized cryptography audit firm (e.g., NCC Group Cryptography Services, Trail of Bits, Cure53, Quarkslab)
Suggested Audit Phases
- Week 1-2: Hybrid KEM construction review, HKDF key derivation, zeroization audit
- Week 2-3: OpenMLS integration, MLS group lifecycle, KeyPackage handling
- Week 3-4: OPAQUE integration, PQ Noise handshake, timing analysis
- Week 4-5: Key Transparency Merkle log, transport security (RPC framing)
- Week 5-6: Report writing, finding triage, remediation discussion