This is an automated email from the ASF dual-hosted git repository.
jvanderzee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new f6be7546bd Clean up ja3_fingerprint continuation handlers (#11462)
f6be7546bd is described below
commit f6be7546bdd890ddd720ee7da2dfcad64d5b3b70
Author: JosiahWI <[email protected]>
AuthorDate: Wed Jun 26 09:30:51 2024 -0500
Clean up ja3_fingerprint continuation handlers (#11462)
* Fix vendor and support email for ja3_fingerprint
This updates the vendor for ja3_fingerprint to Apache Software Foundation
and the support email to [email protected].
* Clean up ja3_fingerprint continuation handlers
* Implement changes requested by Brian Neradt
* Remove redundant function names from log messages
* Rename client_tls_hello_handler to tls_client_hello_handler
* Rename client_tls_close_handler to vconn_close_handler
* Implement changes requested by Brian Neradt
* Put plugin name back in TSError messages
* Implement changes requested by Evan Zelkowitz
* Replace magic values with constants.
---
plugins/ja3_fingerprint/ja3_fingerprint.cc | 210 ++++++++++++++++-------------
1 file changed, 116 insertions(+), 94 deletions(-)
diff --git a/plugins/ja3_fingerprint/ja3_fingerprint.cc
b/plugins/ja3_fingerprint/ja3_fingerprint.cc
index e6d86f4324..95e34c44d6 100644
--- a/plugins/ja3_fingerprint/ja3_fingerprint.cc
+++ b/plugins/ja3_fingerprint/ja3_fingerprint.cc
@@ -45,6 +45,17 @@
#include <cstring>
#include <memory>
#include <string>
+#include <utility>
+
+namespace
+{
+//
+constexpr int ja3_hash_included_byte_count{16};
+static_assert(ja3_hash_included_byte_count <= MD5_DIGEST_LENGTH);
+
+constexpr int ja3_hash_hex_string_with_null_terminator_length{2 *
ja3_hash_included_byte_count + 1};
+
+} // end anonymous namespace
const char *PLUGIN_NAME = "ja3_fingerprint";
static DbgCtl dbg_ctl{PLUGIN_NAME};
@@ -56,8 +67,22 @@ static int global_modify_incoming_enabled = 0;
struct ja3_data {
std::string ja3_string;
- char md5_string[33];
+ char md5_string[ja3_hash_hex_string_with_null_terminator_length];
char ip_addr[INET6_ADDRSTRLEN];
+
+ char const *
+ update_fingerprint()
+ {
+ // Validate that the buffer is the same size as we will be writing into.
+ static_assert(ja3_hash_hex_string_with_null_terminator_length ==
sizeof(this->md5_string));
+
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5(reinterpret_cast<unsigned char const *>(this->ja3_string.c_str()),
this->ja3_string.length(), digest);
+ for (int i{0}; i < ja3_hash_included_byte_count; ++i) {
+ std::snprintf(&(this->md5_string[i * 2]), sizeof(this->md5_string) - (i
* 2), "%02x", static_cast<unsigned int>(digest[i]));
+ }
+ return this->md5_string;
+ }
};
struct ja3_remap_info {
@@ -163,57 +188,84 @@ append_to_field(TSMBuffer bufp, TSMLoc hdr_loc, const
char *field, int field_len
TSHandleMLocRelease(bufp, hdr_loc, target);
}
-static int
-client_hello_ja3_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void
*edata)
+static ja3_data *
+create_ja3_data(TSVConn const ssl_vc)
{
- TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
- switch (event) {
- case TS_EVENT_SSL_CLIENT_HELLO: {
- TSSslConnection sslobj = TSVConnSslConnectionGet(ssl_vc);
+ ja3_data *result = new ja3_data;
+ std::string const raw_ja3_string{custom_get_ja3(reinterpret_cast<SSL
*>(TSVConnSslConnectionGet(ssl_vc)))};
+ result->ja3_string = std::move(raw_ja3_string);
+ getIP(TSNetVConnRemoteAddrGet(ssl_vc), result->ip_addr);
+ return result;
+}
- // OpenSSL handle
- SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+static int
+tls_client_hello_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void
*edata)
+{
+ if (TS_EVENT_SSL_CLIENT_HELLO != event) {
+ Dbg(dbg_ctl, "Unexpected event %d.", event);
+ // We ignore the event, but we don't want to reject the connection.
+ return TS_SUCCESS;
+ }
- ja3_data *data = new ja3_data;
- data->ja3_string.append(custom_get_ja3(ssl));
- getIP(TSNetVConnRemoteAddrGet(ssl_vc), data->ip_addr);
+ TSVConn const ssl_vc{static_cast<TSVConn>(edata)};
+ ja3_data *ja3_vconn_data{create_ja3_data(ssl_vc)};
+ TSUserArgSet(ssl_vc, ja3_idx, static_cast<void *>(ja3_vconn_data));
+ Dbg(dbg_ctl, "JA3 raw: %s", ja3_vconn_data->ja3_string.c_str());
+ char const *fingerprint{ja3_vconn_data->update_fingerprint()};
+ Dbg(dbg_ctl, "JA3 fingerprint: %s", fingerprint);
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
- TSUserArgSet(ssl_vc, ja3_idx, static_cast<void *>(data));
- Dbg(dbg_ctl, "client_hello_ja3_handler(): JA3: %s",
data->ja3_string.c_str());
+static int
+vconn_close_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
+{
+ if (TS_EVENT_VCONN_CLOSE != event) {
+ Dbg(dbg_ctl, "Unexpected event %d.", event);
+ // We ignore the event, but we don't want to reject the connection.
+ return TS_SUCCESS;
+ }
- // MD5 hash
- unsigned char digest[MD5_DIGEST_LENGTH];
- MD5((unsigned char *)data->ja3_string.c_str(), data->ja3_string.length(),
digest);
+ TSVConn const ssl_vc{static_cast<TSVConn>(edata)};
+ delete static_cast<ja3_data *>(TSUserArgGet(ssl_vc, ja3_idx));
+ TSUserArgSet(ssl_vc, ja3_idx, nullptr);
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
- // Validate that the buffer is the same size as we will be writing into
(compile time)
- static_assert((16 * 2 + 1) == sizeof(data->md5_string));
- for (int i = 0; i < 16; i++) {
- snprintf(&(data->md5_string[i * 2]), sizeof(data->md5_string) - (i * 2),
"%02x", static_cast<unsigned int>(digest[i]));
- }
- Dbg(dbg_ctl, "Fingerprint: %s", data->md5_string);
- break;
+static void
+modify_ja3_headers(TSCont contp, TSHttpTxn txnp, ja3_data const
*ja3_vconn_data)
+{
+ // Decide global or remap
+ ja3_remap_info *remap_info = static_cast<ja3_remap_info
*>(TSContDataGet(contp));
+ bool raw_flag = remap_info ? remap_info->raw_enabled :
global_raw_enabled;
+ bool log_flag = remap_info ? remap_info->log_enabled :
global_log_enabled;
+ Dbg(dbg_ctl, "Found ja3 string.");
+
+ // Get handle to headers
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ if (global_modify_incoming_enabled) {
+ TSAssert(TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc));
+ } else {
+ TSAssert(TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &bufp, &hdr_loc));
}
- case TS_EVENT_VCONN_CLOSE: {
- // Clean up
- ja3_data *data = static_cast<ja3_data *>(TSUserArgGet(ssl_vc, ja3_idx));
-
- if (data == nullptr) {
- Dbg(dbg_ctl, "client_hello_ja3_handler(): Failed to retrieve ja3 data at
VCONN_CLOSE.");
- return TS_ERROR;
- }
- TSUserArgSet(ssl_vc, ja3_idx, nullptr);
+ // Add JA3 md5 fingerprints
+ append_to_field(bufp, hdr_loc, "X-JA3-Sig", 9, ja3_vconn_data->md5_string,
32);
- delete data;
- break;
- }
- default: {
- Dbg(dbg_ctl, "client_hello_ja3_handler(): Unexpected event.");
- break;
+ // If raw string is configured, added JA3 raw string to header as well
+ if (raw_flag) {
+ append_to_field(bufp, hdr_loc, "x-JA3-RAW", 9,
ja3_vconn_data->ja3_string.data(), ja3_vconn_data->ja3_string.size());
}
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+
+ // Write to logfile
+ if (log_flag) {
+ TSTextLogObjectWrite(pluginlog, "Client IP: %s\tJA3: %.*s\tMD5: %.*s",
ja3_vconn_data->ip_addr,
+ static_cast<int>(ja3_vconn_data->ja3_string.size()),
ja3_vconn_data->ja3_string.data(), 32,
+ ja3_vconn_data->md5_string);
}
- TSVConnReenable(ssl_vc);
- return TS_SUCCESS;
}
static int
@@ -221,54 +273,26 @@ req_hdr_ja3_handler(TSCont contp, TSEvent event, void
*edata)
{
TSEvent expected_event = global_modify_incoming_enabled ?
TS_EVENT_HTTP_READ_REQUEST_HDR : TS_EVENT_HTTP_SEND_REQUEST_HDR;
if (event != expected_event) {
- TSError("[%s] req_hdr_ja3_handler(): Unexpected event, got %d, expected
%d", PLUGIN_NAME, event, expected_event);
+ TSError("[%s] Unexpected event, got %d, expected %d", PLUGIN_NAME, event,
expected_event);
TSAssert(event == expected_event);
}
- TSHttpTxn txnp = nullptr;
- TSHttpSsn ssnp = nullptr;
- TSVConn vconn = nullptr;
+ TSHttpTxn txnp{};
+ TSHttpSsn ssnp{};
+ TSVConn vconn{};
if ((txnp = static_cast<TSHttpTxn>(edata)) == nullptr || (ssnp =
TSHttpTxnSsnGet(txnp)) == nullptr ||
(vconn = TSHttpSsnClientVConnGet(ssnp)) == nullptr) {
- Dbg(dbg_ctl, "req_hdr_ja3_handler(): Failure to retrieve txn/ssn/vconn
object.");
+ Dbg(dbg_ctl, "Failure to retrieve txn/ssn/vconn object.");
TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
return TS_SUCCESS;
}
// Retrieve ja3_data from vconn args
- ja3_data *data = static_cast<ja3_data *>(TSUserArgGet(vconn, ja3_idx));
- if (data) {
- // Decide global or remap
- ja3_remap_info *remap_info = static_cast<ja3_remap_info
*>(TSContDataGet(contp));
- bool raw_flag = remap_info ? remap_info->raw_enabled :
global_raw_enabled;
- bool log_flag = remap_info ? remap_info->log_enabled :
global_log_enabled;
- Dbg(dbg_ctl, "req_hdr_ja3_handler(): Found ja3 string.");
-
- // Get handle to headers
- TSMBuffer bufp;
- TSMLoc hdr_loc;
- if (global_modify_incoming_enabled) {
- TSAssert(TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc));
- } else {
- TSAssert(TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &bufp, &hdr_loc));
- }
-
- // Add JA3 md5 fingerprints
- append_to_field(bufp, hdr_loc, "X-JA3-Sig", 9, data->md5_string, 32);
-
- // If raw string is configured, added JA3 raw string to header as well
- if (raw_flag) {
- append_to_field(bufp, hdr_loc, "x-JA3-RAW", 9, data->ja3_string.data(),
data->ja3_string.size());
- }
- TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
-
- // Write to logfile
- if (log_flag) {
- TSTextLogObjectWrite(pluginlog, "Client IP: %s\tJA3: %.*s\tMD5: %.*s",
data->ip_addr,
- static_cast<int>(data->ja3_string.size()),
data->ja3_string.data(), 32, data->md5_string);
- }
+ ja3_data const *ja3_vconn_data = static_cast<ja3_data *>(TSUserArgGet(vconn,
ja3_idx));
+ if (ja3_vconn_data) {
+ modify_ja3_headers(contp, txnp, ja3_vconn_data);
} else {
- Dbg(dbg_ctl, "req_hdr_ja3_handler(): ja3 data not set. Not SSL vconn.
Abort.");
+ Dbg(dbg_ctl, "ja3 data not set. Not SSL vconn. Abort.");
}
TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
return TS_SUCCESS;
@@ -288,19 +312,19 @@ read_config_option(int argc, const char *argv[], int
&raw, int &log, int &modify
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "",
longopts, nullptr)) >= 0) {
switch (opt) {
case '?':
- Dbg(dbg_ctl, "read_config_option(): Unrecognized command arguments.");
+ Dbg(dbg_ctl, "Unrecognized command arguments.");
case 0:
case -1:
break;
default:
- Dbg(dbg_ctl, "read_config_option(): Unexpected options error.");
+ Dbg(dbg_ctl, "Unexpected options error.");
return false;
}
}
- Dbg(dbg_ctl, "read_config_option(): ja3 raw is %s", (raw == 1) ? "enabled" :
"disabled");
- Dbg(dbg_ctl, "read_config_option(): ja3 logging is %s", (log == 1) ?
"enabled" : "disabled");
- Dbg(dbg_ctl, "read_config_option(): ja3 modify-incoming is %s",
(modify_incoming == 1) ? "enabled" : "disabled");
+ Dbg(dbg_ctl, "ja3 raw is %s", (raw == 1) ? "enabled" : "disabled");
+ Dbg(dbg_ctl, "ja3 logging is %s", (log == 1) ? "enabled" : "disabled");
+ Dbg(dbg_ctl, "ja3 modify-incoming is %s", (modify_incoming == 1) ? "enabled"
: "disabled");
return true;
}
@@ -312,8 +336,8 @@ TSPluginInit(int argc, const char *argv[])
TSPluginRegistrationInfo info;
info.plugin_name = PLUGIN_NAME;
- info.vendor_name = "Oath";
- info.support_email = "[email protected]";
+ info.vendor_name = "Apache Software Foundation";
+ info.support_email = "[email protected]";
// Options
if (!read_config_option(argc, argv, global_raw_enabled, global_log_enabled,
global_modify_incoming_enabled)) {
@@ -328,10 +352,9 @@ TSPluginInit(int argc, const char *argv[])
Dbg(dbg_ctl, "log object created successfully");
}
// SNI handler
- TSCont ja3_cont = TSContCreate(client_hello_ja3_handler, nullptr);
TSUserArgIndexReserve(TS_USER_ARGS_VCONN, PLUGIN_NAME, "used to pass ja3",
&ja3_idx);
- TSHttpHookAdd(TS_SSL_CLIENT_HELLO_HOOK, ja3_cont);
- TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, ja3_cont);
+ TSHttpHookAdd(TS_SSL_CLIENT_HELLO_HOOK,
TSContCreate(tls_client_hello_handler, nullptr));
+ TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, TSContCreate(vconn_close_handler,
nullptr));
TSHttpHookID const hook = global_modify_incoming_enabled ?
TS_HTTP_READ_REQUEST_HDR_HOOK : TS_HTTP_SEND_REQUEST_HDR_HOOK;
TSHttpHookAdd(hook, TSContCreate(req_hdr_ja3_handler, nullptr));
@@ -348,15 +371,14 @@ TSRemapInit(TSRemapInterface * /* api_info ATS_UNUSED */,
char * /* errbuf ATS_U
// Check if there is config conflict as both global and remap plugin
if (ja3_idx >= 0) {
- TSError(PLUGIN_NAME, "TSRemapInit(): JA3 configured as both global and
remap. Check plugin.config.");
+ TSError("[%s] JA3 configured as both global and remap. Check
plugin.config.", PLUGIN_NAME);
return TS_ERROR;
}
// Set up SNI handler for all TLS connections
- TSCont ja3_cont = TSContCreate(client_hello_ja3_handler, nullptr);
TSUserArgIndexReserve(TS_USER_ARGS_VCONN, PLUGIN_NAME, "Used to pass ja3",
&ja3_idx);
- TSHttpHookAdd(TS_SSL_CLIENT_HELLO_HOOK, ja3_cont);
- TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, ja3_cont);
+ TSHttpHookAdd(TS_SSL_CLIENT_HELLO_HOOK,
TSContCreate(tls_client_hello_handler, nullptr));
+ TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, TSContCreate(vconn_close_handler,
nullptr));
return TS_SUCCESS;
}
@@ -371,7 +393,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char
* /* errbuf ATS_UNUSE
int discard_modify_incoming = -1; // Not used for remap.
if (!read_config_option(argc - 1, const_cast<const char **>(argv + 1),
remap_info->raw_enabled, remap_info->log_enabled,
discard_modify_incoming)) {
- Dbg(dbg_ctl, "TSRemapNewInstance(): Bad arguments");
+ Dbg(dbg_ctl, "Bad arguments");
return TS_ERROR;
}
@@ -396,7 +418,7 @@ TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo
*rri)
// On remap, set up handler at send req hook to send JA3 data as header
if (!remap_info || !rri || !(remap_info->handler)) {
- TSError("[%s] TSRemapDoRemap(): Invalid private data or RRI or handler.",
PLUGIN_NAME);
+ TSError("[%s] Invalid private data or RRI or handler.", PLUGIN_NAME);
} else {
TSHttpTxnHookAdd(rh, TS_HTTP_SEND_REQUEST_HDR_HOOK, remap_info->handler);
}