Skip to content

JA4H: Rust includes HTTP/2 pseudo-headers in header count and hash #306

Description

@elpy1

Summary

The Rust JA4H implementation includes HTTP/2 pseudo-headers such as :method,
:scheme, :authority, and :path in both the JA4H header count and header
hash. Python and Wireshark exclude these fields, so Rust produces different JA4H
fingerprints for the same HTTP/2 request.

Affected code

Rust collects http2.header.name and filters only cookie and referer:

rust/ja4/src/http.rs:125-145:

let headers = http2
    .values("http2.header.name")
    .filter_map(|s| {
        if s == "cookie" {
            has_cookie_header = true;
            None
        } else if s == "referer" {
            has_referer_header = true;
            None
        } else {
            Some(s.to_owned())
        }
    })
    .collect();

Other implementations:

  • Python drops names beginning with : in python/ja4h.py:47-50.
  • Wireshark drops pseudo-headers in wireshark/source/packet-ja4.c:1195-1208,
    because splitting a pseudo-header name on : yields an empty first component
    and the code rejects empty names.

Evidence

For pcap/chrome-cloudflare-quic-with-secrets.pcapng, Python and Wireshark
agree on the HTTP/2 JA4H:

ge20nn12enus_60f823d07c94_000000000000_000000000000

References:

  • python/test/testdata/chrome-cloudflare-quic-with-secrets.pcapng.json
  • wireshark/test/testdata/chrome-cloudflare-quic-with-secrets.pcapng.json

Rust emits:

ge20nn16enus_0f5a7a41a252_000000000000_000000000000

Reference:

  • rust/ja4/src/snapshots/ja4__insta@chrome-cloudflare-quic-with-secrets.pcapng.snap

The first segment differs (ge20nn12enus vs ge20nn16enus), consistent with
Rust counting four HTTP/2 pseudo-headers.

Expected behavior

I assume HTTP/2 pseudo-headers should not contribute to:

  • the JA4H header count
  • the JA4H header-name hash
  • raw ja4h_r / ja4h_ro header-name lists

Suggested resolution

  1. Align implementations - filter pseudo-headers in HttpStats::from_http2,
    before adding names to the regular header list.

  2. Add a regression test using an HTTP/2 request containing the common pseudo
    headers and assert that the Rust output matches Python/Wireshark for the
    header count and header hash.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions