first commit

This commit is contained in:
bryan
2025-12-30 13:53:13 -06:00
commit f0e8d9400a
31 changed files with 12464 additions and 0 deletions

165
overview.md Normal file
View File

@@ -0,0 +1,165 @@
# Secure Chat: Project Specification & Zero-Knowledge Architecture
This document outlines the full architecture for a self-hosted, single-server, Discord-style replacement using the Zero-Knowledge model (MEGA.nz inspired).
---
## 1. CORE TECH STACK
* **Backend:** Node.js (Express or Fastify) + Socket.io (Real-time).
* **Desktop:** Electron (React/Vue frontend) + Node `crypto` module.
* **Database:** PostgreSQL (Persistent data/Key bundles) + Redis (Real-time presence/Typing).
* **Storage:** Local Filesystem or MinIO (Encrypted file blobs).
* **Media:** WebRTC (P2P for Voice/Video) with mandatory DTLS/SRTP.
---
## 2. ACCOUNT LIFECYCLE (ZERO-KNOWLEDGE)
### A. Account Creation
1. **User Input:** Username + Password.
2. **Entropy:** Client (Electron) generates a random 128-bit **Master Key (MK)** and a random **Salt**.
3. **Derivation:** Client runs `PBKDF2-HMAC-SHA-512` (100k+ iterations) on Password + Salt.
- Result = 256-bit Key.
- **DEK (Derived Encryption Key):** Bits 0-127.
- **DAK (Derived Authentication Key):** Bits 128-255.
4. **Locking the MK:** Client encrypts MK using DEK via `AES-GCM`.
5. **Auth Proof:** Client hashes the DAK: `HAK = SHA-256(DAK)`.
6. **Key Generation:** Client generates RSA-2048 (Sharing) and Ed25519 (Signing) pairs. Private keys are encrypted with the unlocked MK.
7. **Server Storage:** Server receives and stores: `Username`, `Salt`, `Encrypted MK`, `HAK`, `Encrypted Private Keys`, and `Raw Public Keys`.
### B. Login Handshake
1. **Salt Request:** Client asks for `Salt` for `Username`.
- *Security Fix:* Server returns a fake deterministic salt if the user doesn't exist to prevent enumeration.
2. **Local Compute:** Client computes `DAK` from password and received salt.
3. **Authentication:** Client sends `DAK` to server.
4. **Verification:** Server checks if `SHA-256(DAK) == HAK`.
5. **Retrieval:** Server sends `Encrypted MK` and `Encrypted Private Keys`.
6. **Decryption:** Client uses `DEK` to unlock the `MK`, then uses `MK` to unlock Private Keys.
### C. Password Recovery
- During setup, the user exports the raw **Master Key** (Recovery Key).
- To reset: User provides the Recovery Key + New Password. The client generates a new DEK from the new password and re-encrypts the Master Key for the server.
### D. Session Persistence (Local Security)
- To avoid re-entering passwords on every app launch, the **Master Key** is encrypted with a unique **Local Machine Key** and stored in the OS Keychain (using `electron-keytar`).
- This keeps the MK safe on the physical disk even if the machine is stolen, as it requires OS-level user authentication to retrieve.
---
## 3. SECURITY & REAL-TIME FIXES
### A. Identity Protection
- **Problem:** Attackers shouldn't know which usernames exist.
- **Solution:** `FakeSalt = HMAC(ServerSecret, Username)`. Always return a salt, even for non-existent users.
### B. Forward Secrecy (Key Rotation)
- **Problem:** Kicked users shouldn't read future messages.
- **Solution:** When a user leaves, the Admin client generates a new **Channel Key**. It encrypts this new key for all remaining members using their Public Keys.
### C. Trust Verification (MITM Protection)
- Users can verify each other via **Safety Numbers** (Fingerprints). These are short strings derived from their Public Keys. If the numbers match on both users' screens, the connection is confirmed as un-intercepted by the server.
### D. Electron Hardening
- `contextIsolation: true` and `sandbox: true`.
- Use a `preload.js` script to expose only necessary crypto functions via `contextBridge`.
---
## 4. END-TO-END ENCRYPTION (E2EE) LOGIC
### Message Integrity (Digital Signatures)
- Every message is signed by the sender's **Ed25519 Private Key**.
- The recipient verifies the signature using the sender's **Public Key** stored on the server. This prevents the server from tampering with or replaying encrypted messages.
---
## 5. DATABASE SCHEMA (POSTGRESQL)
```sql
-- Core User Table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT UNIQUE NOT NULL,
client_salt TEXT NOT NULL,
encrypted_master_key TEXT NOT NULL, -- MK encrypted by DEK
hashed_auth_key TEXT NOT NULL, -- SHA256(DAK)
public_identity_key TEXT NOT NULL, -- RSA Public Key for Encryption
public_signing_key TEXT NOT NULL, -- Ed25519 Public Key for Signatures
is_admin BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
-- Permission Roles
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL, -- 'admin', 'moderator', 'member'
permissions JSONB -- e.g. {"can_view_history": true}
);
-- Channel Key Bundles (The bridge to E2EE)
CREATE TABLE channel_keys (
channel_id UUID NOT NULL,
user_id UUID NOT NULL,
encrypted_key_bundle TEXT NOT NULL, -- Channel Key encrypted for this specific user
key_version INTEGER DEFAULT 1, -- For rotation tracking
PRIMARY KEY (channel_id, user_id)
);
-- Message Storage
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL,
sender_id UUID NOT NULL,
ciphertext TEXT NOT NULL, -- Encrypted content
nonce TEXT NOT NULL, -- AES Initialization Vector
signature TEXT NOT NULL, -- Ed25519 Signature
key_version INTEGER NOT NULL, -- Link to specific key bundle
created_at TIMESTAMP DEFAULT NOW()
);
## 5. ELECTRON PRELOAD (SECURE BRIDGE)
```javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
const crypto = require('node:crypto');
contextBridge.exposeInMainWorld('cryptoAPI', {
// Perform heavy PBKDF2 asynchronously to keep the UI responsive
deriveAuthKeys: (password, salt) => {
return new Promise((resolve, reject) => {
const iterations = 100000;
crypto.pbkdf2(password, salt, iterations, 32, 'sha512', (err, derived) => {
if (err) reject(err);
resolve({
dek: derived.slice(0, 16).toString('hex'),
dak: derived.slice(16, 32).toString('hex')
});
});
});
},
encryptData: (plaintext, keyHex, iv) => {
const key = Buffer.from(keyHex, 'hex');
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
return { content: encrypted, tag: cipher.getAuthTag().toString('hex') };
},
signMessage: (privateKey, message) => {
return crypto.sign(null, Buffer.from(message), privateKey).toString('hex');
}
});
```
## 6. REAL-TIME FEATURES
- **Presence:** `user:status:{id}` stored in Redis with 60s TTL. Client sends heartbeat every 30s.
- **Typing:** Socket.io event typing_start -> Room broadcast. Frontend clears name after 5s silence.
- **DMs:** Use Signal Protocol's Double Ratchet. Server stores encrypted pre-key bundles.
- **Files:** Encrypted with a unique File Key (AES-256). The File Key is sent inside the E2EE text message to the channel/user.