End-to-End Encryption
Zeq OS provides transparent end-to-end encryption between browser clients and the API server. Once enabled, all HTTP requests and responses are encrypted with AES-256-GCM — the server and client negotiate a shared key via a PBKDF2 handshake, and all subsequent traffic is encrypted automatically.
Architecture
| Component | Implementation | Purpose |
|---|---|---|
| Cipher | AES-256-GCM | Authenticated encryption with 128-bit tags |
| Key Derivation | PBKDF2-SHA256 | 100,000 iterations from shared secret + nonces |
| Wire Format | iv(12) + authTag(16) + ciphertext | Same format as HITE |
| Content Type | application/x-zeq-e2e | Signals encrypted body |
| Session TTL | 1 hour | Automatic cleanup every 10 minutes |
Handshake Flow
Browser API Server
| |
| POST /api/e2e/handshake |
| { clientNonce: <32 bytes hex> } ──────────> |
| |
| Server generates serverNonce |
| Derives session key via PBKDF2 |
| Stores session with 1-hour TTL |
| |
| <──────── { sessionId, serverNonce } |
| |
| Client derives same key via PBKDF2 |
| (secret + clientNonce + serverNonce) |
| |
| All subsequent requests use encrypted bodies |
| Content-Type: application/x-zeq-e2e |
| Accept: application/x-zeq-e2e |
| X-E2E-Session: <sessionId> |
| |
Both sides derive the same AES-256-GCM key from the shared secret (ZEQ_E2E_SECRET) combined with both nonces. The nonces ensure each session gets a unique key.
Client Library
The client library at framework/lib/zeq-e2e-client.js is a zero-dependency ES module that works in any modern browser via the Web Crypto API.
Auto-Wrap Mode
The simplest integration — monkey-patches window.fetch for transparent encryption:
<script type="module">
import { zeqE2EHandshake, zeqE2EWrap } from '/static/zeq-e2e-client.js';
try {
await zeqE2EHandshake(window.location.origin);
zeqE2EWrap();
// All fetch() calls are now automatically encrypted
} catch (e) {
// Graceful fallback — plaintext continues to work
}
</script>
Explicit Fetch Mode
For finer control, use zeqE2EFetch as a drop-in replacement for fetch():
import { zeqE2EHandshake, zeqE2EFetch } from '/static/zeq-e2e-client.js';
await zeqE2EHandshake(window.location.origin);
// Encrypted GET
const operators = await zeqE2EFetch('/api/zeq/operators');
const data = await operators.json();
// Encrypted POST
const result = await zeqE2EFetch('/api/zeq/operators/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 'KO42', params: { t: 1000 } })
});
WebSocket E2E
Encrypted WebSocket connections are supported via session query parameter:
import { zeqE2EHandshake, zeqE2EWebSocket } from '/static/zeq-e2e-client.js';
await zeqE2EHandshake(window.location.origin);
const ws = zeqE2EWebSocket('ws://localhost:8080/ws/pulse');
ws.onmessage = (event) => {
// Messages are automatically decrypted
const tick = JSON.parse(event.data);
console.log(`Phase: ${tick.phase}`);
};
Server Configuration
E2E encryption is enabled by setting the ZEQ_E2E_SECRET environment variable:
# .env — leave empty to disable E2E (graceful degradation)
ZEQ_E2E_SECRET=your-e2e-secret-here
If ZEQ_E2E_SECRET is not set, the server falls back to ZEQ_HITE_SECRET. If neither is set, the handshake endpoint returns an error and clients fall back to plaintext.
Wire Format
Encrypted payloads use the same binary format as HITE:
[IV: 12 bytes] [Auth Tag: 16 bytes] [Ciphertext: variable]
- IV: Random 12-byte initialization vector (unique per message)
- Auth Tag: 128-bit GCM authentication tag (integrity + authenticity)
- Ciphertext: AES-256-GCM encrypted payload
Request/Response Flow
Encrypted Request
- Client serializes the request body to JSON
- Client encrypts with the session key → binary payload
- Client sends with
Content-Type: application/x-zeq-e2eandX-E2E-Session: <id> - Server middleware decrypts before route handlers see the request
Encrypted Response
- Route handler returns a normal JSON response
- Server middleware checks for
Accept: application/x-zeq-e2eheader + valid session - Server encrypts the JSON response with the session key
- Client library automatically decrypts the response
Plaintext Fallback
Requests without E2E headers continue to work normally. E2E is opt-in per client — zero breaking changes for existing integrations.
App Integration
All Zeq OS browser applications include the E2E bootstrap snippet. On startup, each app attempts a handshake and wraps fetch(). If the handshake fails (server not configured, network error), the app continues with plaintext — no user-visible errors.
Security Properties
| Property | Guarantee |
|---|---|
| Confidentiality | AES-256-GCM encryption on all request/response bodies |
| Integrity | GCM authentication tags detect tampering |
| Forward secrecy | Each session uses unique nonces → unique key |
| Session isolation | Sessions cannot decrypt each other's traffic |
| Graceful degradation | Plaintext continues to work if E2E is unavailable |