v158 · network · webtransport
WebTransport keying material export
A TLS session already holds a secret both ends agree on — so why run a second key exchange just to bind your application protocol to the connection? Chrome 158 adds WebTransport.exportKeyingMaterial(label, context, outputLength), exposing the TLS keying material exporter (RFC 8446 §7.5, via RFC 9001 for QUIC) as a Promise<Uint8Array> of secret bytes derived from, and unique to, the established session.
concepts
-
The exporter surface, probed exactly
Probe
typeof WebTransport.prototype.exportKeyingMaterial, its three-argument arity, and its descriptor — then make a real call on a real (unestablished) session and watch the returned promise reject with the genuineInvalidStateErrorthe spec requires when the session fails instead of establishing. -
Three RangeError gates, tested before the network
Label over 255 bytes, context over 255 bytes, output length of 0 or above the implementation ceiling — each must reject with
RangeErrorbefore the session's state is even consulted. Dial the sizes yourself and fire real calls; the argument checks run even on a session that never connected. -
Bind a key to a real session
The product flow end to end: your endpoint URL, your exporter label and context, a real connection attempt, a real export call, and a hex dump of whatever genuinely comes back — secret bytes on an established session, or the exact error when establishment fails. Nothing simulated.
-
From exported bytes to a working MAC
What the bytes are for: feed keying material into WebCrypto —
importKey, HMAC-sign a challenge, verify it — the channel-binding pattern RFC 9266 formalised. The pipeline runs for real in any browser; the page is explicit about whether its input bytes came from a live TLS exporter or a clearly-labelled stand-in.
why it shipped
Applications keep needing a secret that proves "this message belongs to this connection": binding an authentication token so it cannot be replayed over another channel, deriving an application-layer encryption key without a second handshake, or implementing channel binding for a protocol tunnelled over WebTransport. TLS has had the answer since RFC 5705 — the keying material exporter derives application-specific secrets from the session's master secret without ever exposing the traffic keys — but the web platform offered no way to reach it.
exportKeyingMaterial() closes that gap with deliberate limits: label and context are capped at 255 bytes each, output length runs from 1 byte up to an implementation ceiling that must be at least 4096 (Chromium: exactly 1–4096), and the call only resolves once the session is established — a session that fails or closes rejects with InvalidStateError. Chromium additionally mixes the WebTransport CONNECT stream ID into the exporter context, so two WebTransport sessions pooled onto one HTTP/3 connection still derive different keys even with identical label and context arguments.
the API
const transport = new WebTransport("https://example.com:4433/app");
await transport.ready;
const enc = new TextEncoder();
const key = await transport.exportKeyingMaterial(
enc.encode("EXPORTER-my-app v1"), // label, ≤ 255 bytes
enc.encode(sessionNonce), // context, ≤ 255 bytes
32, // outputLength: 1 … ≥4096 (impl-defined)
);
// key: Uint8Array(32) — secret, session-bound, same on both ends
Both ends of the connection can derive the same bytes by invoking their TLS stack's exporter with the same label, context, and length — that is the whole point. Argument validation (the three RangeError gates) happens before the state check, and a "closed"/"failed" session — or one that fails while the call is pending — rejects with InvalidStateError.