TLS Fingerprints & Client Certificates

Control the TLS ClientHello and present mTLS certificates in replay.

Anti-bot platforms score the TLS ClientHello before any application data arrives: cipher suites, extension order, ALPN, and GREASE placement collapse into JA3/JA4 hashes that gate traffic, so a replay with perfect headers over a stock TLS stack still fails. Traffic Jam’s replay rebuilds the captured client’s ClientHello — or a preset browser/app profile — through uTLS, preserves captured header order, and presents mTLS client certificates on either transport.

Replay transports

Every replay (open a request in the API lab → Replay) picks a transport:

TransportClientHelloHeader order
native (default) — Native Go transportThe running Go toolchain’s own helloGo sorts headers; order diverges from the capture
utls — Fingerprint-faithful (uTLS)Rebuilt from a profile (below)Captured order written byte for byte on HTTP/1.1

The native transport’s JA3/JA4 is recorded once per process against a local listener with the same ALPN set. The uTLS transport dials with a single-use HelloCustom spec built fresh per connection.

Select a ClientHello profile

In the replay dialog, set Transport to Fingerprint-faithful (uTLS), then pick a ClientHello profile:

  • capturedCaptured client (session fingerprint), the default on the uTLS transport; rebuilds the hello from the session’s captured TLS fingerprint. If the exchange has no captured fingerprint, the dialog warns and the profile fails at replay time — choose a preset instead.
  • golangGo (native stack), the native Go hello rebuilt through uTLS; a control for A/B testing TLS fingerprint gating.
  • Preset profiles — real client hellos from uTLS: Chrome (133, 131, 120, 120-pq, 115-pq, 106, 100, 83, 70), Firefox (120, 105, 102, 99, 65, 55), Safari/iOS (16.0, 14, 13, 12.1, 11.1), and others (android-11-okhttp, edge-85, 360-7.5, qq-11.1).

Each profile’s JA3/JA4 is computed from its spec and served by GET /api/replay/transports.

The Force HTTP/1.1 checkbox rewrites the spec to offer only http/1.1 in ALPN and drops the HTTP/2 application_settings extension, so the captured header order survives byte for byte.

How the backend reproduces the ClientHello

Building a spec from the captured fingerprint preserves what fingerprinting systems observe and drops what a fresh session cannot carry:

  • Extension order is preserved — it is part of JA3 and JA4.
  • GREASE is regenerated — per-connection values; uTLS emits fresh ones, excluded from fingerprints.
  • pre_shared_key (0x0029) and early_data (0x002a) are dropped — a fresh session cannot resume captured PSKs.
  • Key shares are synthesized from the captured supported groups: hybrid post-quantum clients (X25519MLKEM768/X25519Kyber768) offer the hybrid share plus X25519; everyone else offers one share for their preferred classical group.
  • Cipher suites, supported groups, EC point formats, signature algorithms, supported_versions, and ALPN are rebuilt from the capture; compress_certificate replays with brotli (the Chrome-family offering); unknown extensions replay presence-only.

JA3 is the MD5 of legacyVersion,ciphers,extensions,groups,ecPointFormats with GREASE filtered; JA4 is the FoxIO a_b_c form.

When ALPN negotiates h2 (and Force HTTP/1.1 is off), the uTLS transport writes the HTTP/2 preface itself instead of Go’s fixed h2 stack: the captured client SETTINGS frame — IDs, values, and order — verbatim, plus the captured pseudo-header order (the Akamai-style HTTP/2 fingerprint).

Preview the fingerprint before sending

The replay dialog fetches a captured-vs-replay report (POST /api/replay/fingerprint-report) that never touches the network. It shows Captured and Replay will send sides — JA3, JA4, ALPN, TLS version, HTTP version, User-Agent, header order, h2 SETTINGS — plus mismatches:

DimensionSeverityFlagged when
ja4highJA4 differs — antibot gates (Cloudflare, Akamai, DataDome) compare it first
ja3warningJA3 differs — still checked by older WAF rulesets
alpn / httpVersion / userAgentwarningALPN lists differ; an h2 capture would replay as HTTP/1.1; User-Agent disagrees with the ClientHello
headerOrder / h2SettingswarningOrder diverges (native transport sorts headers); native transport cannot reproduce the captured SETTINGS frame
tlsSession / headerSet / egressinfoPSK dropped, header set differs, proxy egress noted

Upload mTLS client certificates

Open Settings → mTLS client certificates. Certificates are stored in the local SQLite database; API responses never return the PEM.

  1. Enter a Name and Pinned hosts (comma-separated, e.g. api-m.goldapple.ru).
  2. Supply one of two formats:
    • Combined PEM — certificate chain followed by the private key (a PRIVATE KEY, RSA PRIVATE KEY, or EC PRIVATE KEY block). Uploads without a private key are rejected.
    • …or a .p12/.pfx file with its password — base64-encoded in the browser, unlocked server-side and re-encoded as PEM.
  3. Click Store certificate. PEM takes precedence when both are provided. Upload limit is 1 MiB.

Host pinning matches the replay target hostname: an exact match wins over a suffix match, and among suffix matches the longest (most specific) wins.

Attach a certificate in replay

In the replay dialog, set Client certificate (mTLS) to Auto (host-pinned) (the default — the backend attaches the certificate pinned to the target host, if one exists) or to a specific certificate by name. Both transports present the certificate when the server issues a TLS CertificateRequest; the replay response echoes the clientCertId actually used.

[!TIP] Bulk replay is capped at 50 requests per batch.

Rejection classes

Every replay result is classified, giving mutation probes, canary checks, and bulk runs a stable verdict vocabulary:

ClassMeaning
(empty) — accepted2xx; not a rejection
transport-errorDial or TLS handshake failure — no HTTP status
client-certificate-requiredHTTP 400 whose body matches the nginx mTLS gate (“No required SSL certificate…”)
unauthorized / forbidden / not-found401 / 403 / 404
rate-limited429
antifraud-reject451 — the anti-fraud layer refused the request outright
redirect3xx — replay never follows redirects
client-error / server-errorany other 4xx / 5xx

client-certificate-required is a body sniff of the first 4096 bytes of a 400, distinguishing the missing-mTLS-cert gate from an ordinary bad request. The diagnosis is in the pair: if attaching a certificate flips client-certificate-required to 2xx or antifraud-reject, the mTLS gate is passed and the next gate is application-level. Canary checks alert on rejection-class drift.

[!NOTE] Replay does not follow redirects, defaults to a 30 s timeout, and excludes captured credentials unless you opt in — see /docs/en/security/. The captured JA3/JA4 come from the TLS analysis tab (/docs/en/analysis/comparing/).

Traffic Jam documentation. Built with Hugo.