Skip to content

Commit 3f6f207

Browse files
Add BoringSSL support to the ja4_fingerprint plugin (#12914)
* got client hello routed to plugins * Creates ja4 fingerprint with boringssl * cleanup a bit * make ssl_client_hello const * spaces cleanup * cleanup code * more cleanup * Update plugin.cc * Update plugin.cc * Update ts.h * Update apidefs.h.in * Update to make more clean * Update data * address comments * Update ja4_fingerprint.en.rst * Add docs * Update TSVConnClientHelloGet.en.rst * Update TSVConnClientHelloGet.en.rst * Address comments * Update TSClientHello.en.rst * Update TSClientHello.en.rst * Eliminate heap allocations * Address Copilot comments * Address Copilot comments * Update doxygen comments --------- Co-authored-by: Jasmine Emanouel <jasmine.nahrain@gmail.com>
1 parent 37a801a commit 3f6f207

12 files changed

Lines changed: 748 additions & 49 deletions

File tree

cmake/ExperimentalPlugins.cmake

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,7 @@ auto_option(HOOK_TRACE FEATURE_VAR BUILD_HOOK_TRACE DEFAULT ${_DEFAULT})
4242
auto_option(HTTP_STATS FEATURE_VAR BUILD_HTTP_STATS DEFAULT ${_DEFAULT})
4343
auto_option(ICAP FEATURE_VAR BUILD_ICAP DEFAULT ${_DEFAULT})
4444
auto_option(INLINER FEATURE_VAR BUILD_INLINER DEFAULT ${_DEFAULT})
45-
auto_option(
46-
JA4_FINGERPRINT
47-
FEATURE_VAR
48-
BUILD_JA4_FINGERPRINT
49-
VAR_DEPENDS
50-
HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
51-
DEFAULT
52-
${_DEFAULT}
53-
)
45+
auto_option(JA4_FINGERPRINT FEATURE_VAR BUILD_JA4_FINGERPRINT VAR_DEPENDS DEFAULT ${_DEFAULT})
5446
auto_option(
5547
MAGICK
5648
FEATURE_VAR

doc/admin-guide/plugins/index.en.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
178178
Header Frequency <header_freq.en>
179179
Hook Trace <hook-trace.en>
180180
ICAP <icap.en>
181+
JA4 Fingerprint <ja4_fingerprint.en>
181182
Maxmind ACL <maxmind_acl.en>
182183
Memcache <memcache.en>
183184
Memory Profile <memory_profile.en>
@@ -232,6 +233,9 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
232233
:doc:`ICAP <icap.en>`
233234
Pass response data to external server for further processing using the ICAP protocol.
234235

236+
:doc:`JA4 Fingerprint <ja4_fingerprint.en>`
237+
Calculates JA4 Fingerprints for incoming TLS traffic.
238+
235239
:doc:`MaxMind ACL <maxmind_acl.en>`
236240
ACL based on the maxmind geo databases (GeoIP2 mmdb and libmaxminddb)
237241

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one
2+
or more contributor license agreements. See the NOTICE file
3+
distributed with this work for additional information
4+
regarding copyright ownership. The ASF licenses this file
5+
to you under the Apache License, Version 2.0 (the
6+
"License"); you may not use this file except in compliance
7+
with the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing,
12+
software distributed under the License is distributed on an
13+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
KIND, either express or implied. See the License for the
15+
specific language governing permissions and limitations
16+
under the License.
17+
18+
.. include:: ../../common.defs
19+
20+
.. _admin-plugins-ja4-fingerprint:
21+
22+
JA4 Fingerprint Plugin
23+
**********************
24+
25+
Description
26+
===========
27+
28+
The JA4 Fingerprint plugin generates TLS client fingerprints based on the JA4
29+
algorithm designed by John Althouse. JA4 is the successor to the JA3
30+
fingerprinting algorithm and provides improved client identification for TLS
31+
connections.
32+
33+
A JA4 fingerprint uniquely identifies TLS clients based on characteristics of
34+
their TLS ClientHello messages, including:
35+
36+
* TLS version
37+
* ALPN (Application-Layer Protocol Negotiation) preferences
38+
* Cipher suites offered
39+
* TLS extensions present
40+
41+
This information can be used for:
42+
43+
* Client identification and tracking
44+
* Bot detection and mitigation
45+
* Security analytics and threat intelligence
46+
* Understanding client TLS implementation patterns
47+
48+
How It Works
49+
============
50+
51+
The plugin intercepts TLS ClientHello messages during the TLS handshake and
52+
generates a JA4 fingerprint consisting of three sections separated by underscores:
53+
54+
**Section a (unhashed)**: Basic information about the client including:
55+
56+
* Protocol (``t`` for TCP, ``q`` for QUIC)
57+
* TLS version
58+
* SNI (Server Name Indication) status
59+
* Number of cipher suites
60+
* Number of extensions
61+
* First ALPN value
62+
63+
**Section b (hashed)**: A SHA-256 hash of the sorted cipher suite list
64+
65+
**Section c (hashed)**: A SHA-256 hash of the sorted extension list
66+
67+
Example fingerprint::
68+
69+
t13d1516h2_8daaf6152771_b186095e22b6
70+
71+
Key Differences from JA3
72+
-------------------------
73+
74+
* Cipher suites and extensions are sorted before hashing for consistency
75+
* SNI and ALPN information is included in the fingerprint
76+
* More resistant to fingerprint randomization
77+
78+
Plugin Configuration
79+
====================
80+
81+
The plugin operates as a global plugin and has no configuration options.
82+
83+
To enable the plugin, add the following line to :file:`plugin.config`::
84+
85+
ja4_fingerprint.so
86+
87+
No additional parameters are required or supported.
88+
89+
Plugin Behavior
90+
===============
91+
92+
When loaded, the plugin will:
93+
94+
1. **Capture TLS ClientHello**: Intercepts all incoming TLS connections during
95+
the ClientHello phase
96+
97+
2. **Generate Fingerprint**: Calculates the JA4 fingerprint from the
98+
ClientHello data
99+
100+
3. **Log to File**: Writes the fingerprint and client IP address to
101+
``ja4_fingerprint.log``
102+
103+
4. **Add HTTP Headers**: Injects the following headers into subsequent HTTP
104+
requests on the same connection:
105+
106+
* ``ja4``: Contains the JA4 fingerprint
107+
* ``x-ja4-via``: Contains the proxy name (from ``proxy.config.proxy_name``)
108+
109+
Log Output
110+
==========
111+
112+
The plugin writes to ``ja4_fingerprint.log`` in the Traffic Server log
113+
directory (typically ``/var/log/trafficserver/``).
114+
115+
**Log Format**::
116+
117+
[timestamp] Client IP: <ip_address> JA4: <fingerprint>
118+
119+
**Example**::
120+
121+
[Jan 29 10:15:23.456] Client IP: 192.168.1.100 JA4: t13d1516h2_8daaf6152771_b186095e22b6
122+
[Jan 29 10:15:24.123] Client IP: 10.0.0.50 JA4: t13d1715h2_8daaf6152771_02713d6af862
123+
124+
Using JA4 Headers in Origin Requests
125+
=====================================
126+
127+
Origin servers can access the JA4 fingerprint through the injected HTTP header.
128+
This allows the origin to:
129+
130+
* Make access control decisions based on client fingerprints
131+
* Log fingerprints for security analysis
132+
* Track client populations and TLS implementation patterns
133+
134+
The ``x-ja4-via`` header allows origin servers to track which Traffic Server
135+
proxy handled the request when multiple proxies are deployed.
136+
137+
Debugging
138+
=========
139+
140+
To enable debug logging for the plugin, set the following in :file:`records.yaml`::
141+
142+
records:
143+
diags:
144+
debug:
145+
enabled: 1
146+
tags: ja4_fingerprint
147+
148+
Debug output will appear in :file:`diags.log` and includes:
149+
150+
* ClientHello processing events
151+
* Fingerprint generation details
152+
* Header injection operations
153+
154+
Requirements
155+
============
156+
157+
* Traffic Server must be built with TLS support (OpenSSL or BoringSSL)
158+
* The plugin operates on all TLS connections
159+
160+
Configuration Settings
161+
======================
162+
163+
The plugin requires the ``proxy.config.proxy_name`` setting to be configured
164+
for the ``x-ja4-via`` header. If not set, the plugin will log an error and use
165+
"unknown" as the proxy name.
166+
167+
To set the proxy name in :file:`records.yaml`::
168+
169+
records:
170+
proxy:
171+
config:
172+
proxy_name: proxy01
173+
174+
Limitations
175+
===========
176+
177+
* The plugin only operates in global mode (no per-remap configuration)
178+
* Logging cannot be disabled
179+
* Raw (unhashed) cipher and extension lists are not logged
180+
* Non-TLS connections do not generate fingerprints
181+
182+
See Also
183+
========
184+
185+
* JA4 Technical Specification: https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md
186+
* JA4 is licensed under the BSD 3-Clause license
187+
188+
Example Configuration
189+
=====================
190+
191+
Complete example configuration for enabling JA4 fingerprinting:
192+
193+
**plugin.config**::
194+
195+
ja4_fingerprint.so
196+
197+
**records.yaml**::
198+
199+
records:
200+
proxy:
201+
config:
202+
proxy_name: proxy-01
203+
diags:
204+
debug:
205+
enabled: 1
206+
tags: ja4_fingerprint
207+
208+
After restarting Traffic Server, the plugin will begin fingerprinting TLS
209+
connections and logging to ``ja4_fingerprint.log``.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one or more
2+
contributor license agreements. See the NOTICE file distributed
3+
with this work for additional information regarding copyright
4+
ownership. The ASF licenses this file to you under the Apache
5+
License, Version 2.0 (the "License"); you may not use this file
6+
except in compliance with the License. You may obtain a copy of
7+
the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
implied. See the License for the specific language governing
15+
permissions and limitations under the License.
16+
17+
.. include:: ../../../common.defs
18+
19+
.. default-domain:: cpp
20+
21+
TSVConnClientHelloGet
22+
*********************
23+
24+
Synopsis
25+
========
26+
27+
.. code-block:: cpp
28+
29+
#include <ts/ts.h>
30+
31+
.. function:: TSClientHello TSVConnClientHelloGet(TSVConn sslp)
32+
.. function:: TSReturnCode TSClientHelloExtensionGet(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen)
33+
34+
Description
35+
===========
36+
37+
:func:`TSVConnClientHelloGet` retrieves ClientHello message data from the TLS
38+
virtual connection :arg:`sslp`. Returns a :type:`TSClientHello` always. The availability
39+
of the returned object must be checked before use.
40+
41+
.. important::
42+
43+
This function should only be called from the ``TS_EVENT_SSL_CLIENT_HELLO`` hook.
44+
The returned :type:`TSClientHello` is only valid during the SSL ClientHello event processing.
45+
Using this function from other hooks may result in accessing invalid or stale data.
46+
47+
:func:`TSClientHelloExtensionGet` retrieves extension data for the specified
48+
:arg:`type` (e.g., ``0x10`` for ALPN). Returns :enumerator:`TS_SUCCESS` if
49+
found, :enumerator:`TS_ERROR` otherwise. The returned pointer in :arg:`out` is
50+
valid only while :arg:`ch` exists.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one or more
2+
contributor license agreements. See the NOTICE file distributed
3+
with this work for additional information regarding copyright
4+
ownership. The ASF licenses this file to you under the Apache
5+
License, Version 2.0 (the "License"); you may not use this file
6+
except in compliance with the License. You may obtain a copy of
7+
the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
implied. See the License for the specific language governing
15+
permissions and limitations under the License.
16+
17+
.. include:: ../../../common.defs
18+
19+
.. default-domain:: cpp
20+
21+
TSClientHello
22+
*************
23+
24+
Synopsis
25+
========
26+
27+
.. code-block:: cpp
28+
29+
#include <ts/apidefs.h>
30+
31+
.. type:: TSClientHello
32+
33+
.. type:: TSClientHello::TSExtensionTypeList
34+
35+
A type alias for an iterable container of extension type IDs.
36+
37+
38+
Description
39+
===========
40+
41+
:type:`TSClientHello` is an opaque handle to a TLS ClientHello message sent by
42+
a client during the TLS handshake. It provides access to the client's TLS
43+
version, cipher suites, and extensions.
44+
45+
The implementation abstracts differences between OpenSSL and BoringSSL to
46+
provide a consistent interface.
47+
48+
Accessor Methods
49+
================
50+
51+
The following methods are available to access ClientHello data:
52+
53+
.. function:: bool is_available() const
54+
55+
Returns whether the object contains valid values. As long as
56+
:func:`TSVConnClientHelloGet` is called for a TLS connection, the return
57+
value should be `true`.
58+
59+
.. function:: uint16_t get_version() const
60+
61+
Returns the TLS version from the ClientHello message.
62+
63+
.. function:: const uint8_t* get_cipher_suites() const
64+
65+
Returns a pointer to the cipher suites buffer. The length is available via
66+
:func:`get_cipher_suites_len()`.
67+
68+
.. function:: size_t get_cipher_suites_len() const
69+
70+
Returns the length of the cipher suites buffer in bytes.
71+
72+
.. function:: TSClientHello::TSExtensionTypeList get_extension_types() const
73+
74+
Returns an iterable container of extension type IDs present in the ClientHello.
75+
This method abstracts the differences between BoringSSL (which uses an extensions
76+
buffer) and OpenSSL (which uses an extension_ids array), providing a consistent
77+
interface regardless of the SSL library in use.
78+
79+
.. function:: void* _get_internal() const
80+
81+
Returns a pointer to internal implementation data. This is an internal accessor for advanced use
82+
cases. This accessor is not part of the stable public API, and plugins must not cast or rely
83+
on the returned pointer type.

0 commit comments

Comments
 (0)