Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions cmake/ExperimentalPlugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,7 @@ auto_option(HOOK_TRACE FEATURE_VAR BUILD_HOOK_TRACE DEFAULT ${_DEFAULT})
auto_option(HTTP_STATS FEATURE_VAR BUILD_HTTP_STATS DEFAULT ${_DEFAULT})
auto_option(ICAP FEATURE_VAR BUILD_ICAP DEFAULT ${_DEFAULT})
auto_option(INLINER FEATURE_VAR BUILD_INLINER DEFAULT ${_DEFAULT})
auto_option(
JA4_FINGERPRINT
FEATURE_VAR
BUILD_JA4_FINGERPRINT
VAR_DEPENDS
HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
DEFAULT
${_DEFAULT}
)
auto_option(JA4_FINGERPRINT FEATURE_VAR BUILD_JA4_FINGERPRINT VAR_DEPENDS DEFAULT ${_DEFAULT})
auto_option(
MAGICK
FEATURE_VAR
Expand Down
4 changes: 4 additions & 0 deletions doc/admin-guide/plugins/index.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
Header Frequency <header_freq.en>
Hook Trace <hook-trace.en>
ICAP <icap.en>
JA4 Fingerprint <ja4_fingerprint.en>
Maxmind ACL <maxmind_acl.en>
Memcache <memcache.en>
Memory Profile <memory_profile.en>
Expand Down Expand Up @@ -228,6 +229,9 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
:doc:`ICAP <icap.en>`
Pass response data to external server for further processing using the ICAP protocol.

:doc:`JA4 Fingerprint <ja4_fingerprint.en>`
Calculates JA4 Fingerprints for incoming TLS traffic.

:doc:`MaxMind ACL <maxmind_acl.en>`
ACL based on the maxmind geo databases (GeoIP2 mmdb and libmaxminddb)

Expand Down
209 changes: 209 additions & 0 deletions doc/admin-guide/plugins/ja4_fingerprint.en.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.

.. include:: ../../common.defs

.. _admin-plugins-ja4-fingerprint:

JA4 Fingerprint Plugin
**********************

Description
===========

The JA4 Fingerprint plugin generates TLS client fingerprints based on the JA4
algorithm designed by John Althouse. JA4 is the successor to the JA3
fingerprinting algorithm and provides improved client identification for TLS
connections.

A JA4 fingerprint uniquely identifies TLS clients based on characteristics of
their TLS ClientHello messages, including:

* TLS version
* ALPN (Application-Layer Protocol Negotiation) preferences
* Cipher suites offered
* TLS extensions present

This information can be used for:

* Client identification and tracking
* Bot detection and mitigation
* Security analytics and threat intelligence
* Understanding client TLS implementation patterns

How It Works
============

The plugin intercepts TLS ClientHello messages during the TLS handshake and
generates a JA4 fingerprint consisting of three sections separated by underscores:

**Section a (unhashed)**: Basic information about the client including:

* Protocol (``t`` for TCP, ``q`` for QUIC)
* TLS version
* SNI (Server Name Indication) status
* Number of cipher suites
* Number of extensions
* First ALPN value

**Section b (hashed)**: A SHA-256 hash of the sorted cipher suite list

**Section c (hashed)**: A SHA-256 hash of the sorted extension list

Example fingerprint::

t13d1516h2_8daaf6152771_b186095e22b6

Key Differences from JA3
-------------------------

* Cipher suites and extensions are sorted before hashing for consistency
* SNI and ALPN information is included in the fingerprint
* More resistant to fingerprint randomization

Plugin Configuration
====================

The plugin operates as a global plugin and has no configuration options.

To enable the plugin, add the following line to :file:`plugin.config`::

ja4_fingerprint.so

No additional parameters are required or supported.

Plugin Behavior
===============

When loaded, the plugin will:

1. **Capture TLS ClientHello**: Intercepts all incoming TLS connections during
the ClientHello phase

2. **Generate Fingerprint**: Calculates the JA4 fingerprint from the
ClientHello data

3. **Log to File**: Writes the fingerprint and client IP address to
``ja4_fingerprint.log``

4. **Add HTTP Headers**: Injects the following headers into subsequent HTTP
requests on the same connection:

* ``ja4``: Contains the JA4 fingerprint
* ``x-ja4-via``: Contains the proxy name (from ``proxy.config.proxy_name``)

Log Output
==========

The plugin writes to ``ja4_fingerprint.log`` in the Traffic Server log
directory (typically ``/var/log/trafficserver/``).

**Log Format**::

[timestamp] Client IP: <ip_address> JA4: <fingerprint>

**Example**::

[Jan 29 10:15:23.456] Client IP: 192.168.1.100 JA4: t13d1516h2_8daaf6152771_b186095e22b6
[Jan 29 10:15:24.123] Client IP: 10.0.0.50 JA4: t13d1715h2_8daaf6152771_02713d6af862

Using JA4 Headers in Origin Requests
=====================================

Origin servers can access the JA4 fingerprint through the injected HTTP header.
This allows the origin to:

* Make access control decisions based on client fingerprints
* Log fingerprints for security analysis
* Track client populations and TLS implementation patterns

The ``x-ja4-via`` header allows origin servers to track which Traffic Server
proxy handled the request when multiple proxies are deployed.

Debugging
=========

To enable debug logging for the plugin, set the following in :file:`records.yaml`::

records:
diags:
debug:
enabled: 1
tags: ja4_fingerprint

Debug output will appear in :file:`diags.log` and includes:

* ClientHello processing events
* Fingerprint generation details
* Header injection operations

Requirements
============

* Traffic Server must be built with TLS support (OpenSSL or BoringSSL)
* The plugin operates on all TLS connections

Configuration Settings
======================

The plugin requires the ``proxy.config.proxy_name`` setting to be configured
for the ``x-ja4-via`` header. If not set, the plugin will log an error and use
"unknown" as the proxy name.

To set the proxy name in :file:`records.yaml`::

records:
proxy:
config:
proxy_name: proxy01

Limitations
===========

* The plugin only operates in global mode (no per-remap configuration)
* Logging cannot be disabled
* Raw (unhashed) cipher and extension lists are not logged
* Non-TLS connections do not generate fingerprints

See Also
========

* JA4 Technical Specification: https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md
* JA4 is licensed under the BSD 3-Clause license

Example Configuration
=====================

Complete example configuration for enabling JA4 fingerprinting:

**plugin.config**::

ja4_fingerprint.so

**records.yaml**::

records:
proxy:
config:
proxy_name: proxy-01
diags:
debug:
enabled: 1
tags: ja4_fingerprint

After restarting Traffic Server, the plugin will begin fingerprinting TLS
connections and logging to ``ja4_fingerprint.log``.
50 changes: 50 additions & 0 deletions doc/developer-guide/api/functions/TSVConnClientHelloGet.en.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.. Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed
with this work for additional information regarding copyright
ownership. The ASF licenses this file to you under the Apache
License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of
the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.

.. include:: ../../../common.defs

.. default-domain:: cpp

TSVConnClientHelloGet
*********************

Synopsis
========

.. code-block:: cpp

#include <ts/ts.h>

.. function:: TSClientHello TSVConnClientHelloGet(TSVConn sslp)
.. function:: void TSClientHelloDestroy(TSClientHello ch)
.. function:: TSReturnCode TSClientHelloExtensionGet(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen)

Description
===========

:func:`TSVConnClientHelloGet` retrieves ClientHello message data from the TLS
virtual connection :arg:`sslp`. This function is typically called from the
Copy link
Member

@maskit maskit Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't deep copy the information, we can use this function only on limited hooks. I'm sure we can use TS_EVENT_SSL_CLIENT_HELLO, but I'm not sure if it's the only hook or there are other hooks.

The documentation should list up all the hooks, or tell that TS_EVENT_SSL_CLIENT_HELLO is the only hook supported (maybe for now, until somebody confirms another hook work as well).

``TS_EVENT_SSL_CLIENT_HELLO`` hook. Returns ``nullptr`` if
:arg:`sslp` is invalid or not a TLS connection.

The caller must call :func:`TSClientHelloDestroy` to free the returned object.

:func:`TSClientHelloDestroy` frees the :type:`TSClientHello` object :arg:`ch`.

:func:`TSClientHelloExtensionGet` retrieves extension data for the specified
:arg:`type` (e.g., ``0x10`` for ALPN). Returns :enumerator:`TS_SUCCESS` if
found, :enumerator:`TS_ERROR` otherwise. The returned pointer in :arg:`out` is
valid only while :arg:`ch` exists.
43 changes: 43 additions & 0 deletions doc/developer-guide/api/types/TSClientHello.en.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.. Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed
with this work for additional information regarding copyright
ownership. The ASF licenses this file to you under the Apache
License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of
the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.

.. include:: ../../../common.defs

.. default-domain:: cpp

TSClientHello
*************

Synopsis
========

.. code-block:: cpp

#include <ts/apidefs.h>

.. type:: TSClientHello


Description
===========

:type:`TSClientHello` is an opaque handle to a TLS ClientHello message sent by
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just a handle. I think the accessors need to be listed. Otherwise reading the header file would be the only way to know how to use it.

a client during the TLS handshake. It provides access to the client's TLS
version, cipher suites, and extensions.

Objects of this type are obtained via :func:`TSVConnClientHelloGet` and must
be freed using :func:`TSClientHelloDestroy`. The implementation abstracts
differences between OpenSSL and BoringSSL to provide a consistent interface.
11 changes: 7 additions & 4 deletions include/iocore/net/TLSSNISupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class TLSSNISupport
/**
* @return 1 if successful
*/
int getExtension(int type, const uint8_t **out, size_t *outlen);
int getExtension(int type, const uint8_t **out, size_t *outlen);
ClientHelloContainer get_client_hello_container();

private:
ClientHelloContainer _chc;
Expand All @@ -55,8 +56,9 @@ class TLSSNISupport
static TLSSNISupport *getInstance(SSL *ssl);
static void bind(SSL *ssl, TLSSNISupport *snis);
static void unbind(SSL *ssl);

int perform_sni_action(SSL &ssl);
int perform_sni_action(SSL &ssl);
ClientHelloContainer get_client_hello_container() const;
void set_client_hello_container(ClientHelloContainer container);
// Callback functions for OpenSSL libraries

/** Process a CLIENT_HELLO from a client.
Expand Down Expand Up @@ -114,5 +116,6 @@ class TLSSNISupport
// Null-terminated string, or nullptr if there is no SNI server name.
std::unique_ptr<char[]> _sni_server_name;

void _set_sni_server_name_buffer(std::string_view name);
void _set_sni_server_name_buffer(std::string_view name);
ClientHelloContainer _chc = nullptr;
};
Loading