Summary
technical_details/JA4.md defines the first character of a client JA4
fingerprint as the transport context:
q for QUIC
t for TLS over TCP
The Rust client JA4 implementation uses extension 57 (QUIC transport params)
to derive this instead of connection context, making it diverge from the
documented algorithm, and Python and Zeek implementations.
Affected code
Rust client JA4 sets the marker from extension 57:
rust/ja4/src/tls.rs:318
rust/ja4/src/tls.rs:493
let quic = quic_marker(exts.contains(&TLS_EXT_QUIC_TRANSPORT_PARAMETERS));
const TLS_EXT_QUIC_TRANSPORT_PARAMETERS: u16 = 57;
The helper then emits q or t:
rust/ja4/src/tls.rs:481-487:
fn quic_marker(is_quic: bool) -> char {
if is_quic {
'q'
} else {
't'
}
}
Other client implementations
Python client JA4 derives q/t from packet connectiong context. TCP packets
set x['quic'] = False, while UDP packets with a QUIC layer set result in
True (python/ja4.py:514-533).
Client JA4 then uses that (python/ja4.py:217-220):
ptype = 'q' if x['quic'] else 't'
Zeek also uses connection context. It emits q only when the connection
protocol is UDP and the service contains QUIC (zeek/ja4/main.zeek:63-72):
if (c?$conn && c$conn$proto == udp && "QUIC" in c$service) {
proto = "q";
} else {
proto = "t";
}
Impact
A ClientHello observed over TCP with extension 57 would be:
- Rust JA4:
q...
- Python JA4:
t...
- Zeek JA4:
t...
Resulting in a different fingerprint across implementations.
Suggested resolution
Align implementations with documented algorithm by using packet/protocol
context for determining q/t mmarker.
Summary
technical_details/JA4.mddefines the first character of a client JA4fingerprint as the transport context:
qfor QUICtfor TLS over TCPThe Rust client JA4 implementation uses extension 57 (QUIC transport params)
to derive this instead of connection context, making it diverge from the
documented algorithm, and Python and Zeek implementations.
Affected code
Rust client JA4 sets the marker from extension 57:
rust/ja4/src/tls.rs:318rust/ja4/src/tls.rs:493The helper then emits
qort:rust/ja4/src/tls.rs:481-487:Other client implementations
Python client JA4 derives
q/tfrom packet connectiong context. TCP packetsset
x['quic'] = False, while UDP packets with a QUIC layer set result inTrue(python/ja4.py:514-533).Client JA4 then uses that (
python/ja4.py:217-220):Zeek also uses connection context. It emits
qonly when the connectionprotocol is UDP and the service contains
QUIC(zeek/ja4/main.zeek:63-72):Impact
A ClientHello observed over TCP with extension 57 would be:
q...t...t...Resulting in a different fingerprint across implementations.
Suggested resolution
Align implementations with documented algorithm by using packet/protocol
context for determining
q/tmmarker.