v158 · webtransport · keying material
From exported bytes to a working MAC
Exported keying material is raw secret bytes — the useful artefact is what you build from them. This page runs the channel-binding pattern for real in WebCrypto: import the bytes as an HMAC key, sign a server challenge, verify the tag. When this browser can export from a live session it does; otherwise the pipeline runs on clearly-labelled stand-in bytes, because the crypto is the lesson and the provenance banner never lies about the source.
Chrome targets exportKeyingMaterial() for Chrome 158 behind the WebTransportExportKeyingMaterial runtime flag (--enable-blink-features=WebTransportExportKeyingMaterial, or chrome://flags/#enable-experimental-web-platform-features). A real export additionally needs a reachable HTTP/3 endpoint — set one in the field below. The WebCrypto half of the pipeline is standard and runs in every modern browser.
Run the pipeline
Pipeline not run yet.
Input keying material (hex)
—
HMAC-SHA-256 tag over the challenge (hex)
—
why MAC, not send?
Channel binding (the pattern RFC 9266 names tls-exporter) never transmits the exported bytes. Both ends derive the same secret independently; the client proves possession by MACing a fresh challenge, and the server verifies with its own copy. A stolen bearer token replayed through another connection fails, because the other connection exports different bytes. Chromium strengthens this further for pooled connections by mixing the CONNECT stream ID into the exporter context — two sessions on one connection still derive distinct keys.
// both ends, independently:
const ikm = await transport.exportKeyingMaterial(label, context, 32);
// client:
const key = await crypto.subtle.importKey(
"raw", ikm, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const tag = await crypto.subtle.sign("HMAC", key, encode(challenge));
send({ challenge, tag }); // the tag travels; the key never does
// server: derives the same ikm from ITS TLS stack, verifies the tag.