maskit opened a new issue, #13727: URL: https://github.com/apache/trafficserver/issues/13727
The `ja4_fingerprint` plugin produces JA4 values that don't match the [JA4 spec](https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md) or other JA4 implementations, for two reasons beyond the missing signature algorithms addressed in #13725. `jax_fingerprint --method JA4` isn't affected by either. ## 1. Cipher suites are byte-swapped in the `b` section `add_ciphers()` builds each cipher code with [`make_word(buf[i], buf[i + 1])`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/ja4_fingerprint/plugin.cc#L314), and [`make_word`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/ja4_fingerprint/plugin.cc#L139) treats its first argument as the low byte: ```cpp make_word(unsigned char lowbyte, unsigned char highbyte) { return (static_cast<std::uint16_t>(highbyte) << 8) | lowbyte; } ``` Cipher suites are big-endian on the wire, so every code is swapped (0x1302 becomes 0x0213) before it's sorted and hashed. As a result, the `b` section never matches other JA4 tools for any client. The cipher count in the `a` section is still correct, and GREASE values still get filtered because they read the same either way, so the output looks plausible. `jax_fingerprint` reads the same bytes big-endian ([`tls_client_hello_summary.cc`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/jax_fingerprint/ja4/tls_client_hello_summary.cc#L81)), and its `b` matches the spec. The unit tests don't catch this because they pass cipher values to `add_cipher()` as integers and never go through `make_word`. ### Reproduction Load both plugins and connect once with `openssl s_client`: ```text ja4_fingerprint.so jax_fingerprint.so --standalone --method JA4 --log-filename jax_ja4 ``` For a ClientHello from OpenSSL 3.6 offering 30 cipher suites, the two plugins logged the same `a` and `c` but a different `b`: | Plugin | `b` | |---|---| | `ja4_fingerprint` | `0be778905493` | | `jax_fingerprint` | `1d37bd780c83` | Hashing that ClientHello's cipher list by hand, per the spec, gives `1d37bd780c83`. Hashing the list with each code byte-swapped gives `0be778905493`. ## 2. Empty lists are hashed instead of reported as zeros The spec says `b` is `000000000000` when there are no ciphers, and `c` is `000000000000` when there are no extensions left after removing SNI and ALPN. [`make_JA4_fingerprint()`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/ja4_fingerprint/ja4.h#L156-L158) always hashes the raw string, so an empty list gives `e3b0c44298fc`, the SHA-256 of an empty string. `jax_fingerprint` outputs zeros in both cases. I found this by reading the code and haven't reproduced it with live traffic. A ClientHello with no extensions other than SNI/ALPN, which some TLS 1.2 clients send, should trigger the `c` case. A ClientHello with no cipher suites is malformed and is probably rejected by the TLS library before the plugin runs. ## Impact Fingerprints from `ja4_fingerprint` can't be compared with JA4 values from other sources such as ja4db, threat-intel feeds, or network sensors. Rules built from its own output match only other `ja4_fingerprint` output. Fixing either bug changes the values the plugin produces, so stored fingerprints would need to be regenerated. ## Recommendation Use `jax_fingerprint` with `--method JA4` instead of `ja4_fingerprint`. It doesn't have either bug. With #13725 applied, its whole fingerprint matches the spec's published example and values computed by hand from live ClientHellos. The configuration below gives the same headers and a comparable log: ```text jax_fingerprint.so --standalone --method JA4 --header ja4 --via-header x-ja4-via --log-filename ja4_fingerprint ``` - `--via-header` sets the header to `proxy.config.proxy_name`, as `ja4_fingerprint` does. - To leave `ja4` headers the client already sent in place, which is what `ja4_fingerprint --preserve` does, add `--mode keep`. - Log lines start with `Client:` instead of `Client IP:`, so parsers keyed on that prefix need updating. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
