Skip to main content

Bridge — Browser-Compatible Zeq-SSL

Browsers can't speak Zeq-SSL natively — a symmetric handshake requires a pre-shared credential, and browsers don't carry one. The bridge at /apps/zeq-ssl-bridge/ (default port :3010) is an HTTPS-terminating proxy that speaks Zeq-SSL inward to your fleet.

Topology

browser ──TLS 1.3──▶ bridge :3010 ──Zeq-SSL──▶ destination :3009

├─ holds its own zsm_key + SSL credential
├─ performs the handshake on the request's behalf
└─ writes SSL-HANDSHAKE audit row for forensics

What the bridge does

  • Terminates ordinary TLS 1.3 on :3010 using whatever cert your deployment configured (HTTPS_CERT_FILE / mkcert dev cert).
  • Performs the Zeq-SSL handshake against the destination state machine.
  • Streams request bytes inward as APP_DATA frames; streams response bytes outward as ordinary HTTPS.
  • Writes one SSL-HANDSHAKE audit row per connection (default-on — flip the per-credential audit_handshakes column off for high-volume sites).

What the bridge does NOT do

  • End-to-end authentication. Anything the bridge sends inward is attested as the bridge, not as the originating browser principal. The bridge cannot hold per-user keys without becoming a key custodian — which would defeat the symmetric-only PQ posture.
  • Custom protocol handling. The bridge is a byte-level relay. Application semantics (HTTP method, headers, body parsing) ride through unchanged.

Post-quantum posture

  • Inner Zeq-SSL leg: PQ-safe (AES-256-GCM + HMAC-SHA256 + SHA-256). The record layer was always symmetric/hash-only; as of the ML-KEM Mode-B seeding (below) the key establishment is post-quantum too, so the inner leg is end-to-end PQ — no classical key-agreement step remains.
  • Outer TLS leg: inherits whatever the configured cert chains to. P-256 / RSA → not PQ-safe; ML-KEM hybrids → PQ-safe to the extent the named hybrid is. (Zeq's own edge is ECDHE today; hybrid X25519MLKEM768 is on the roadmap, blocked on an OpenSSL 3.5+ upgrade.)

Mode 1 (native) is PQ-safe end-to-end when the Mode-B seed is established via ML-KEM. Mode 2 (bridge) is PQ-safe at the application layer but inherits the outer TLS's PQ posture for the browser↔bridge hop.

Mode B seed via ML-KEM-768 (post-quantum first contact)

Mode B (first-touch) no longer needs a pre-shared seed delivered out of band. Two machines that share no prior secret establish the Mode-B peer_shared_seed with a post-quantum KEM (NIST FIPS 203 ML-KEM-768, via the audited @noble/post-quantum), entirely at the application layer — no OpenSSL/TLS dependency:

  1. Responder publishes its ML-KEM public key: GET /api/keyex/:slug/pubkey{ public_key, fingerprint, machine_id }.
  2. Initiator (admin) pins the fingerprint and calls POST /api/ssl/peer-seed/keyex/initiate — the api-core encapsulates to the peer's key, derives the Mode-B seed (HKDF over the ML-KEM secret), stores it, and returns the ciphertext + a seed_commitment.
  3. Responder (admin) calls POST /api/ssl/peer-seed/keyex/respond with that ciphertext — decapsulates with its own ML-KEM key, derives the identical seed, stores it. Matching seed_commitment on both sides confirms agreement without either side revealing the seed.

Thereafter the handshake and record layer run unchanged.

Honest boundary: a bare KEM is unauthenticated. This closes harvest-now-decrypt-later (a passive recorder can't recover the seed), but first-contact MITM is defended by the mandatory fingerprint pin — the initiator must pin the responder's published ML-KEM fingerprint (against its observer / the identity layer) or the call is refused (409 fingerprint_mismatch). Mode A (pre-distributed seed) has no online MITM window. Verified live: a two-domain establishment derives an identical seed with no prior secret.

Backing endpoint

The bridge uses POST /api/ssl/handshake/verify to validate its handshake envelope server-side, then mirrors the bytes. The endpoint is wired and tested in Phase 2.1; the :3010 reverse-proxy listener lands in Phase 2.2.

Configuration

The bridge runs as a separate process — apps/zeq-ssl-bridge/server.ts in Phase 2.2 — and pulls its zsm_key from ZSC under ssl_bridge_zsm_key:<bridge_machine_id>. The destination machines are resolved via Host header → state-machine slug lookup.