Cryptic - End-to-End Encrypted Chat System
View SourceAuthor: Cryptic Team
Version: 1.0.0
Overview
Cryptic is an end-to-end encrypted chat system built in Erlang/OTP. It implements WebSocket mTLS communication, real-time messaging, and cryptographic protocols based on Signal's specifications.
Core Features
- WebSocket mTLS - Certificate-based client authentication
- Double Ratchet Protocol - Forward secrecy and break-in recovery
- X3DH Key Agreement - Asynchronous initial session establishment
- X25519 - Elliptic curve Diffie-Hellman key exchange
- ChaCha20-Poly1305 - AEAD encryption via libsodium NIFs
- Blake2b KDF - High-performance key derivation (39x faster than pure Erlang)
- Event Bus Architecture - Pub/sub system for component communication
- GPG-based Onboarding - Certificate issuance via signed CSRs
- SQLite Message Storage - Encrypted chat history with ChaCha20-Poly1305
- Automatic Certificate Renewal - X.509 client certificate lifecycle management
- External TUI Support - Rust-based terminal UI via Erlang distribution protocol
Architecture
┌──────────────────┐ WebSocket mTLS ┌──────────────────┐
│ Client (Alice) │ ←──────────────────────→ │ Client (Bob) │
│ cryptic_console│ │ cryptic_console │
└────────┬─────────┘ └─────────┬────────┘
│ │
│ event_bus pub/sub │
│ │
┌────┴────────────────────────────┐ │
│ │ │
▼ ▼ │
┌──────────────┐ ┌─────────────┐ │
│ ws_client │ ←───────── │ engine │ │
│ (WebSocket) │ │ (Crypto) │ ratchet state │
└──────┬───────┘ └─────────────┘ │
│ │
│ encrypted messages │
│ │
└────────────┬ │
│ │
WebSocket mTLS │
│ │
│ │
│ │
▼ │
┌──────────────────────────────────────────────────────┐ │
│ WebSocket mTLS Server │ │
│ (cryptic_server + cryptic_ws_handler) │ │
│ │ │
│ • Certificate authentication (GPG-signed) │ │
│ • Message routing between clients │ │
│ • CA REST API (certificate issuance/renewal) │ │
│ • GPG key registry (user verification) │ │
└──────────────────────────────────────────────────────┘ │
│
WebSocket mTLS │
│ │
└──────────────────────────────────┘Key Components
Server Components
cryptic_server
Main server application managing WebSocket mTLS server lifecycle, ETS table initialization, and configuration. Supervises both messaging and CA subsystems.
cryptic_ws_handler
Cowboy WebSocket handler managing client connections with mTLS authentication. Routes encrypted messages between users and maintains server-side message queues for offline delivery.
cryptic_ca_* (CA Subsystem)
cryptic_ca_rest_handler- REST API for certificate issuance and renewalcryptic_ca_store- CA certificate/key management and serial number trackingcryptic_ca_cert- X.509 certificate generation with GPG fingerprint SANscryptic_ca_gpg- GPG signature verification for CSR authenticationcryptic_gpg_registry- Stores user GPG public keys for verification
Client Components
cryptic_console
Terminal interface with command parser, ANSI rendering, and asynchronous message handling. Implements two-process architecture: UI process and message receiver process via cryptic_event_bus subscriptions.
cryptic_shell
Line editor with Emacs keybindings, command history (last 100 commands), and secure password input. Operates in raw terminal mode for character-by-character control.
cryptic_ws_client
WebSocket client with automatic reconnection, keepalive timers, and mTLS connection management. Publishes/subscribes to cryptic_event_bus for component decoupling.
Core Cryptographic Components
cryptic_engine
Messaging orchestrator (gen_server) coordinating X3DH and Double Ratchet operations. Implements callback behavior for storage, network, and UI integration. Manages session lifecycle and pending message queues.
cryptic_ratchet_engine
Double Ratchet state machine (gen_statem) with separate states for initiator/responder roles. Handles DH ratchet steps, chain key advancement, and out-of-order message processing.
cryptic_double_ratchet
Protocol implementation with symmetric-key ratcheting, DH ratchet steps, skipped message key store (max 1000 keys), and header encryption. Pure functional implementation with no process state.
cryptic_lib
X3DH implementation, X25519 key operations, ChaCha20-Poly1305 encryption, and key bundle storage. Provides file-based encrypted storage for identity keys and session states.
cryptic_nif
Libsodium bindings via NIFs: X25519 (crypto_scalarmult), ChaCha20-Poly1305 (crypto_aead_chacha20poly1305_ietf), Blake2b KDF (crypto_generichash), and secure random bytes.
Infrastructure Components
cryptic_event_bus
Pub/sub event system (gen_server) with filter-based subscriptions, topic support, automatic dead subscriber cleanup via process monitors, and safe error handling (filter crashes isolated per subscriber).
cryptic_chat_storage
SQLite3 storage backend via esqlite. Stores encrypted messages with ChaCha20-Poly1305 using user passphrase-derived keys. Supports conversation queries, timestamp filtering, and message status tracking.
cryptic_cert_renewal
Automatic certificate renewal monitor (gen_server). Parses X.509 validity periods, calculates renewal trigger times (configurable threshold), generates CSRs using existing keys (RSA/EC support), GPG-signs CSRs, submits to CA REST API, installs new certificates, and triggers WebSocket reconnection. Includes retry logic with exponential backoff and manual renewal API.
cryptic_cert_monitor
Certificate expiration tracking and alerting system for proactive certificate lifecycle management.
cryptic_event_manager
Event logging infrastructure with pluggable handlers for file logging (cryptic_file_logger), console output (cryptic_console_logger), and message-specific logging (cryptic_msg_logger).
Cryptographic Protocol
1. Certificate-based Authentication
- Server runs integrated CA with REST API (
/ca/v1/csr) - New users generate GPG keypair and CSR
- CSR is GPG-signed with user's private key
- Admin approves user by uploading GPG public key to server
- User submits signed CSR to CA REST endpoint
- CA verifies GPG signature against registered public key
- CA issues X.509 certificate with GPG fingerprint in SAN extension
- Certificate used for mTLS WebSocket authentication
- Automatic renewal via
cryptic_cert_renewalbefore expiration
2. X3DH Key Agreement (Initial Session)
- Client generates X25519 identity keypair and signed prekey on startup
- Client uploads prekey bundle to server via WebSocket
- Server stores bundle in ETS table keyed by username
- First message: sender fetches receiver's prekey bundle
- Sender computes X3DH shared secret using ephemeral key
- X3DH output becomes initial root key for Double Ratchet
- Receiver reconstructs shared secret upon receiving X3DH message
- Both parties initialize matching ratchet sessions
3. Double Ratchet Protocol (Ongoing)
Chain Key Ratcheting (Symmetric)
- Separate sending/receiving chains per direction
- Each message advances chain key:
CK_new = KDF(CK_old) - Message keys derived from chain key:
MK = KDF(CK, 0x01) - Chain keys never reused - deleted after derivation
DH Ratchet Steps (Asymmetric)
- Occurs on first message in new direction
- Generate fresh X25519 ephemeral keypair
- Compute new DH shared secret with peer's public key
- Derive new root key and chain key
- Provides break-in recovery and forward secrecy
Out-of-Order Message Handling
- Skipped message key store (max 1000 keys)
- Pre-derive keys for gaps in message sequence
- Messages decrypt correctly even if delivered out-of-order
- Automatic cleanup of old skipped keys
Performance
- Blake2b KDF via NIF: 39x faster than pure Erlang
- ChaCha20-Poly1305 AEAD via libsodium NIF
- All cryptographic operations use hardware acceleration when available
Security Properties
Cryptographic Primitives
- X25519: ECDH key agreement (Curve25519, ~128-bit security)
- ChaCha20-Poly1305: AEAD encryption (256-bit keys, 96-bit nonces)
- Blake2b: Cryptographic hash and KDF (configurable output, faster than SHA-2)
- Libsodium: Industry-standard cryptographic library via NIFs
Forward Secrecy
- Message-level: Each message uses unique derived key, immediately deleted
- Chain-level: Chain keys advance with each message, old keys deleted
- DH ratchet: New ephemeral keypair on direction change
- Past security: Compromise of current keys doesn't affect past messages
Break-in Recovery
- DH ratchet step: Generates new shared secret independent of compromised state
- Recovery time: One round-trip after compromise (when direction changes)
- Bidirectional: Both sending and receiving chains protected
Out-of-Order Delivery
- Skipped keys: Pre-derive and store keys for missing message numbers (max 1000)
- Gap handling: Messages with gaps in sequence numbers handled automatically
- Delayed messages: Messages arriving late still decrypt correctly
- Cleanup: Skipped keys removed after use or on limit exceeded
Authentication
- Certificate-based: X.509 client certificates with GPG-verified identity
- GPG signatures: All certificate requests authenticated with GPG private keys
- SAN extensions: GPG fingerprints embedded in certificate for binding
- Mutual TLS: Both client and server authenticate each other
Storage Security
- Key encryption: Identity keys and session states encrypted with ChaCha20-Poly1305
- Passphrase-derived keys: User passphrase + random salt + Blake2b KDF
- Message history: SQLite database with per-message encryption
- Key isolation: Each user's keys stored in separate directory (
~/.cryptic/username/)
Quick Start
Server Startup
# Start server with WebSocket mTLS and CA subsystem
./scripts/start-server.sh
# Server binds to 0.0.0.0:8443 by default
# CA REST API available at https://localhost:8443/ca/v1/
Client Startup
# Standard console mode
./bin/cryptic -u alice --enable-db
# With custom server
./bin/cryptic -u alice -s example.com -p 9443
# TUI mode (requires cryptic-tui: github.com/etnt/cryptic-tui)
./bin/cryptic -u alice --tui
User Onboarding
# Interactive wizard for new users
./bin/cryptic --onboard
# Steps:
# 1. Generate GPG keypair
# 2. Export GPG public key for admin
# 3. Admin uploads GPG key to server
# 4. User submits GPG-signed CSR
# 5. CA verifies signature and issues certificate
Basic Usage
connect # Connect to server with mTLS
send <user> <message> # Send encrypted message (auto-ratchet)
chat <user> # Enter chat mode with user
list_users # Show registered users
key_status # Display ratchet session info
inbox # Check pending messages
help # Show all commands
:cr # Manual certificate renewalBuilding and Development
Prerequisites
- Erlang/OTP 27+
- Libsodium (via Homebrew, apt, or source)
- Rebar3
- SQLite3 (for message storage)
- GPG (for certificate onboarding)
Build
# macOS
brew install libsodium
# Build
rebar3 compile
# Generate documentation
rebar3 edoc
# or: rebar3 ex_doc
# Run tests
rebar3 eunit
Certificate Authority Setup
cd CA/
make all # Initialize CA
make client # Generate client cert
./scripts/verify-crt.sh certs/alice.crt
./scripts/revoke-cert.sh certs/02.pem
Pre-configured test certificates in CA/client_keys/:
- alice.{crt,key,pem}
- bob.{crt,key,pem}
- charlie.{crt,key,pem}
- admin.{crt,key,pem}
Known Limitations
- Server-side storage: Messages and sessions stored in ETS (in-memory, non-persistent)
- Single prekey bundle: One prekey per user (no rotation implemented)
- GPG dependency: Onboarding requires GPG installed and configured
- Session cleanup: Ratchet sessions persist until client disconnect
- Skipped key limit: Maximum 1000 skipped message keys per session
WebSocket Message Protocol
Client → Server
Certificate/Key Management
{"type": "upload_identity_keys", ...}- Upload X3DH identity keys and prekeys{"type": "get_key_bundle", "username": "bob"}- Request user's public keys
Messaging
{"type": "x3dh", ...}- Initial X3DH message (session establishment){"type": "ratchet", ...}- Double Ratchet message (ongoing conversation){"type": "send_message", ...}- Generic message (engine determines protocol)
User Management
{"type": "list_users"}- Get list of registered users{"type": "user_status", "username": "alice"}- Check if user is online
Server → Client
Connection
{"type": "welcome", ...}- Connection confirmed, username assigned
Certificate/Key Management
{"type": "key_bundle", "username": "bob", ...}- User's public key bundle{"type": "success", ...}- Operation succeeded
Messaging
{"type": "message", ...}- Incoming encrypted message (X3DH or ratchet){"type": "message_sent", ...}- Message delivery confirmed
User Management
{"type": "users", "users": [...]}- Registered users list{"type": "user_status", ...}- User online/offline status{"type": "error", ...}- Error occurred
CA REST API
POST /ca/v1/csr
{
"csr_pem": "-----BEGIN CERTIFICATE REQUEST-----...",
"gpg_fp": "ABCD1234...",
"gpg_sig_b64": "base64_encoded_signature"
}Response (200 OK)
{
"status": "issued",
"cert_pem": "-----BEGIN CERTIFICATE-----...",
"serial": 42,
"expires_at": 1732627533
}Building and Development
Prerequisites
- Erlang/OTP 27+
- Libsodium development libraries
- Rebar3 build tool
Build Commands
# Install dependencies (macOS)
$ brew install libsodium
# Build the application
$ rebar3 compile
# Generate documentation
$ rebar3 edoc
# Run tests
$ rebar3 eunit
API Documentation
For detailed module documentation:
Core
cryptic_server- Server lifecycle and supervisioncryptic_ws_handler- WebSocket connection handling and message routingcryptic_engine- Messaging orchestration and session managementcryptic_ratchet_engine- Double Ratchet state machinecryptic_double_ratchet- Protocol implementationcryptic_lib- X3DH and cryptographic operations
Client
cryptic_console- Terminal UIcryptic_ws_client- WebSocket client with reconnectioncryptic_shell- Line editor with history
Infrastructure
cryptic_event_bus- Pub/sub event systemcryptic_chat_storage- SQLite encrypted message storagecryptic_cert_renewal- Automatic certificate renewalcryptic_nif- Libsodium bindings
CA
cryptic_ca_rest_handler- Certificate issuance REST APIcryptic_ca_gpg- GPG signature verificationcryptic_gpg_registry- User GPG key storage
License
Mozilla Public License Version 2.0