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
:3010using 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_DATAframes; streams response bytes outward as ordinary HTTPS. - Writes one
SSL-HANDSHAKEaudit row per connection (default-on — flip the per-credentialaudit_handshakescolumn 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
X25519MLKEM768is 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:
- Responder publishes its ML-KEM public key:
GET /api/keyex/:slug/pubkey→{ public_key, fingerprint, machine_id }. - 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 + aseed_commitment. - Responder (admin) calls
POST /api/ssl/peer-seed/keyex/respondwith that ciphertext — decapsulates with its own ML-KEM key, derives the identical seed, stores it. Matchingseed_commitmenton 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.