From 1df4b0d26e5d2de0ee8b52a062cea7b8ab654b85 Mon Sep 17 00:00:00 2001 From: MouseRider Date: Wed, 14 Jan 2026 21:41:12 +0800 Subject: [PATCH] test: add ECH and ALPS extension tests Add integration test to verify ECH (0xfe0d) and ALPS (0x44cd) TLS extensions are correctly captured by the JA4 module. Changes: - Add test_ech_alps.py using curl_cffi to impersonate Chrome 136 - Update test_integration.py to include ech_alps test case - Modern Chrome includes both ECH and ALPS extensions natively The test validates that nginx's raw ClientHello parsing correctly captures these extensions and includes them in JA4 fingerprints. --- .github/workflows/integration-test.yaml | 2 +- test/test_ech_alps.py | 35 +++++++++++++++++++++++++ test/test_integration.py | 16 +++++++++-- test/testdata/ech_alps.txt | 28 ++++++++++++++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/test_ech_alps.py create mode 100644 test/testdata/ech_alps.txt diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 7c25c16..d567ba5 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -13,7 +13,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest + pip install pytest curl_cffi - name: Generate self-signed cert run: | diff --git a/test/test_ech_alps.py b/test/test_ech_alps.py new file mode 100644 index 0000000..7ce74d3 --- /dev/null +++ b/test/test_ech_alps.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +""" +Test script for ECH and ALPS TLS extensions using curl_cffi. + +This script uses curl_cffi to impersonate modern Chrome which natively +supports both ECH (0xfe0d) and ALPS (0x44cd) extensions. +""" + +def main(): + """Run the test with curl_cffi.""" + import sys + + try: + import curl_cffi.requests as requests + + # Make HTTPS request with Chrome impersonation + # Modern Chrome versions include both ECH and ALPS extensions + response = requests.get( + "https://localhost", + impersonate="chrome136", + verify=False, # Skip certificate verification for self-signed cert + timeout=10 + ) + + print(response.text, end='') + + except ImportError: + print("ERROR: curl_cffi not installed. Install with: pip install curl_cffi", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"ERROR: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/test/test_integration.py b/test/test_integration.py index fa1326d..00fdb2e 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -13,6 +13,7 @@ ("tls13_h2", ["--http2", "--tls-max", "1.3"]), ("tls12_h11", ["--http1.1", "--tls-max", "1.2"]), ("no_sni_ip", []), # IP literal to avoid SNI + ("ech_alps", ["--python-test"]), # Test ECH and ALPS extensions together ] EXPECTED_DIR = Path(__file__).parent / "testdata" @@ -21,14 +22,25 @@ def run_curl(name: str, args: list[str]) -> str: """ Run curl in the pinned container with given args, - capture stdout, and return it as a string. + or run Python test script for ECH/ALPS test. + Capture stdout and return it as a string. """ + # Check if this is the Python test (ECH+ALPS) + if args and args[0] == "--python-test": + import sys + test_script = Path(__file__).parent / "test_ech_alps.py" + + # Run the Python test script + cmd = [sys.executable, str(test_script)] + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + return result.stdout + + # Standard curl test if name == "no_sni_ip": curl_cmd = f"curl -k -sS https://127.0.0.1" else: curl_cmd = f"curl -k -sS {' '.join(args)} {URL}" - cmd = [ "docker", "run", "--rm", "--network", "host", CURL_IMG, "sh", "-lc", curl_cmd diff --git a/test/testdata/ech_alps.txt b/test/testdata/ech_alps.txt new file mode 100644 index 0000000..658a25c --- /dev/null +++ b/test/testdata/ech_alps.txt @@ -0,0 +1,28 @@ + + JA4: t13d1516h2_8daaf6152771_d8a2da3f94cd + + JA4 String: t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601 + + JA4one: t13d1514h2_8daaf6152771_1e53c2b25e87 + + JA4S: + + JA4S String: + + JA4H: ge11nn14enus_33ac871f7c5e_e3b0c44298fc_e3b0c44298fc + + JA4H String: ge11nn14enus_Host,sec-ch-ua,sec-ch-ua-mobile,sec-ch-ua-platform,Upgrade-Insecure-Requests,User-Agent,Accept,Sec-Fetch-Site,Sec-Fetch-Mode,Sec-Fetch-User,Sec-Fetch-Dest,Accept-Encoding,Accept-Language,Priority__ + + JA4T: + + JA4T String: + + JA4TS: + + JA4TS String: + + JA4X: + + JA4L: 0_0_64 + + \ No newline at end of file