TLS Exporter

RFC 5705 / RFC 9266 TLS Exporter for binding application-layer keys and tokens to the live TLS session. The exporter is a deterministic byte-string derived from the TLS 1.3 master secret (RFC 8446 §7.5) — pulling 32 bytes under the EXPORTER-Channel-Binding label gives the RFC 9266 "tls-exporter" channel-binding identifier.

Operators bind bearer tokens, FAPI 2.0 access-token-bound proofs, DPoP cnf.tbh claims, mTLS-derived auth headers, and session cookies to the exporter so a captured token cannot be replayed across a different TLS session — even if a downstream proxy re-terminates TLS (RFC 8705). The matching node primitive is tls.TLSSocket#exportKeyingMaterial(length, label[, context]).

Validation throws at the call site for sockets that aren't TLS (channel binding has no meaning over plaintext), sockets whose protocol is not TLS 1.3 (RFC 9266 §4 conformance), or out-of-range length values. Mismatched bindings on verifyTokenBinding return false rather than throwing — token-binding mismatch is a normal request-time outcome, not a config bug.

b.tlsExporter.fromSocket(socketOrReq, opts) #

stable0.7.45
{
  {
    label?:   string,    // default "EXPORTER-Channel-Binding"
    length?:  number,    // default 32; bounded 16..255 bytes
    context?: Buffer     // default null (RFC 8446 §7.5 "no context")
  }
}

Extracts a TLS exporter from socketOrReq (either a TLSSocket directly or an HTTP/HTTP/2 request whose .socket is the TLSSocket). Defaults match RFC 9266 §4 — 32-byte length, label EXPORTER-Channel-Binding, no context — yielding the canonical "tls-exporter" channel-binding identifier. Custom labels and lengths pass through for applications defining their own exporter- derived identifiers; length is bounded 16..255 bytes per the keying-material range Node enforces. Throws when the socket is not TLS 1.3 or when the export call fails.

var b = require("blamejs");
var server = b.https.createServer({ key: KEY, cert: CERT }, function (req, res) {
  var exporter = b.tlsExporter.fromSocket(req, { length: 32 });
  res.end("exporter bytes: " + exporter.length);
  // → "exporter bytes: 32"
});
server.listen(0);

b.tlsExporter.bindToken(socketOrReq, token) #

stable0.7.45

Binds an opaque token (string or Buffer) to the current TLS session by hashing SHA3-512(label || exporter || token), where label is "blamejs/tls-exporter/bind/v1". The framework label keeps the resulting digest distinct from any other place the same exporter + token bytes might be hashed (audit-chain rows, derived-hash columns, etc.) so a binding cannot be reinterpreted across primitives. Operators store the returned hex digest alongside the token and compare via verifyTokenBinding on the next request.

var b = require("blamejs");
b.https.createServer({ key: KEY, cert: CERT }, function (req, res) {
  var binding = b.tlsExporter.bindToken(req, "session-token-abc123");
  binding.length;
  // → 128 (SHA3-512 hex digest, 64 bytes × 2 hex chars)
  res.end("ok");
}).listen(0);

b.tlsExporter.verifyTokenBinding(socketOrReq, token, claimedBinding) #

stable0.7.45

Constant-time compare of a previously-issued bindToken digest against a fresh binding computed from the current TLS session. Returns true when the digests match (token belongs to this TLS session) and false on any mismatch — token-binding mismatch is a normal request-time outcome, so this primitive never throws on mismatch. Throws only when socketOrReq is not TLS 1.3 or when the input shape is wrong.

var b = require("blamejs");
b.https.createServer({ key: KEY, cert: CERT }, function (req, res) {
  var stored = b.tlsExporter.bindToken(req, "session-token-abc123");
  var ok = b.tlsExporter.verifyTokenBinding(req, "session-token-abc123", stored);
  ok;
  // → true
  res.end(ok ? "bound" : "mismatch");
}).listen(0);

Last updated 2026-08-08T16:39:49.652Z by seeder.