Skip to content

Add Chrome 150 and Safari 18.5 profiles, with JA4 tests - #413

Open
nihiloid wants to merge 2 commits into
refraction-networking:masterfrom
nihiloid:add-profiles
Open

Add Chrome 150 and Safari 18.5 profiles, with JA4 tests#413
nihiloid wants to merge 2 commits into
refraction-networking:masterfrom
nihiloid:add-profiles

Conversation

@nihiloid

@nihiloid nihiloid commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Profiles

  • HelloChrome_150 and HelloChrome_150_PSK. Chrome 150 sends the ML-DSA signature algorithms 0x0904, 0x0905 and 0x0906 before the classical ones. The rest of the ClientHello matches the HelloChrome_133 profile that ships already. HelloChrome_Auto now selects HelloChrome_150.
  • HelloSafari_18_5. Safari 18.5 sends no ML-KEM group, its only key share is X25519, it offers TLS 1.0 and TLS 1.1 in supported_versions, and it ends with a padding extension.
  • HelloSafari_26_0, added to the existing HelloSafari_26_3 case. Safari 26.0 sends the same ClientHello as Safari 26.3, so the two names share one spec, the way HelloFirefox_55 and HelloFirefox_56 already do.

Tests

u_parrots_ja4_test.go is new. It builds the ClientHello of a profile, computes the JA4 fingerprint from the bytes on the wire, and compares it against a captured value:

Profile JA4
HelloChrome_150 t13d1516h2_8daaf6152771_806a8c22fdea
HelloChrome_150_PSK t13d1517h2_8daaf6152771_a87ad97598a9
HelloChrome_133 t13d1516h2_8daaf6152771_d8a2da3f94cd
HelloSafari_26_3 t13d2013h2_a09f3c656075_7f0f34a4126d
HelloSafari_26_0 t13d2013h2_a09f3c656075_7f0f34a4126d
HelloSafari_18_5 t13d2014h2_a09f3c656075_e42f34c56612
HelloSafari_16_0 t13d2014h2_a09f3c656075_14788d8d241b

The test needs no network and no new dependency. It parses the ClientHello itself and implements JA4 in about 120 lines. Adding another profile to the table is one line, and a profile that holds a pre_shared_key extension is detected from its spec: the test then gives it a session, so the extension goes on the wire.

A second test builds each profile 16 times and checks that the JA4 does not change. JA4 sorts the cipher suites and the extensions, so ShuffleChromeTLSExtensions must not show up in the fingerprint. A fingerprint that moved with the shuffle would identify uTLS rather than the browser.

A third test covers a PSK profile without a session: uTLS then conceals the empty pre_shared_key extension, and the fingerprint becomes the one of the profile without PSK.

Session resumption

u_psk_resumption_test.go is new as well. It runs two connections of a PSK profile against a TLS 1.3 server of this package. The first connection gets a session ticket. The second connection sends the pre_shared_key extension and resumes the session, which works only if uTLS computes the binders over the ClientHello that goes on the wire. A third connection uses HelloChrome_150, which holds no pre_shared_key extension: it must not resume, although the session cache holds a ticket.

The test runs HelloChrome_150_PSK and HelloChrome_114_Padding_PSK_Shuf. The second profile covers the two features that change how the binders are written: a padding extension, whose length comes from the length of the other extensions, and the extension shuffle, which must keep pre_shared_key last. The other PSK profiles follow the same path as HelloChrome_150_PSK. The test needs no network: it uses newLocalListener and the server of this package.

go build ./..., go vet ./... and go test ./... pass.

nihiloid added 2 commits August 29, 2026 13:35
Add HelloChrome_150 and HelloChrome_150_PSK. Chrome 150 sends the ML-DSA
signature algorithms 0x0904, 0x0905 and 0x0906 before the classical ones.
HelloChrome_Auto now selects HelloChrome_150.

Add HelloSafari_18_5. Safari 18.5 sends no ML-KEM group, it offers TLS 1.0 and
TLS 1.1 in supported_versions, and it ends with a padding extension.

Add HelloSafari_26_0 to the HelloSafari_26_3 case. Safari 26.0 sends the same
ClientHello as Safari 26.3.

Add u_parrots_ja4_test.go. It computes the JA4 of each profile and compares it
against a captured value. A second test checks that the extension shuffle keeps
the JA4 the same on every connection.
Add u_psk_resumption_test.go. It runs two connections of a PSK profile against a
TLS 1.3 server of this package. The first connection gets a session ticket. The
second connection sends the pre_shared_key extension and resumes the session,
which works only if utls computes the binders over the ClientHello that goes on
the wire.

The test runs HelloChrome_150_PSK and HelloChrome_114_Padding_PSK_Shuf. The
second profile holds a padding extension, whose length comes from the length of
the other extensions, and it shuffles its extensions, which must keep
pre_shared_key last.

A third connection uses HelloChrome_150, which holds no pre_shared_key
extension. It must not resume, although the session cache holds a ticket.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant