From 786fbaafd13154235a4174eb9fa58efcacd8af94 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Tue, 6 Jan 2026 15:56:35 +1100 Subject: [PATCH 01/11] got client hello routed to plugins --- cmake/ExperimentalPlugins.cmake | 10 +- include/iocore/net/TLSSNISupport.h | 11 +- include/ts/apidefs.h.in | 1 + include/ts/ts.h | 5 +- .../experimental/ja4_fingerprint/plugin.cc | 161 +++++++++++------- src/api/InkAPI.cc | 17 ++ src/iocore/net/SSLUtils.cc | 2 + src/iocore/net/TLSSNISupport.cc | 20 +++ 8 files changed, 152 insertions(+), 75 deletions(-) diff --git a/cmake/ExperimentalPlugins.cmake b/cmake/ExperimentalPlugins.cmake index 487e0bfed16..74347efde92 100644 --- a/cmake/ExperimentalPlugins.cmake +++ b/cmake/ExperimentalPlugins.cmake @@ -42,13 +42,9 @@ 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} + JA4_FINGERPRINT FEATURE_VAR BUILD_JA4_FINGERPRINT VAR_DEPENDS + # HAVE_SSL_CTX_SET_CLIENT_HELLO_CB + DEFAULT ${_DEFAULT} ) auto_option( MAGICK diff --git a/include/iocore/net/TLSSNISupport.h b/include/iocore/net/TLSSNISupport.h index 6897cce36a4..f56223d23b0 100644 --- a/include/iocore/net/TLSSNISupport.h +++ b/include/iocore/net/TLSSNISupport.h @@ -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; @@ -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. @@ -114,5 +116,6 @@ class TLSSNISupport // Null-terminated string, or nullptr if there is no SNI server name. std::unique_ptr _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; }; diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 078ce0eb69c..dda12fa0db6 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1080,6 +1080,7 @@ using TSHostLookupResult = struct tsapi_hostlookupresult *; using TSAIOCallback = struct tsapi_aiocallback *; using TSAcceptor = struct tsapi_net_accept *; using TSRemapPluginInfo = struct tsapi_remap_plugin_info *; +using TSClientHello = struct tsapi_clienthello *; using TSFetchSM = struct tsapi_fetchsm *; diff --git a/include/ts/ts.h b/include/ts/ts.h index 3227f1cf18d..892d58a0c6f 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -1331,8 +1331,9 @@ TSReturnCode TSVConnProtocolEnable(TSVConn connp, const char *protocol_name); int TSVConnIsSsl(TSVConn sslp); /* Returns 1 if a certificate was provided in the TLS handshake, 0 otherwise. */ -int TSVConnProvidedSslCert(TSVConn sslp); -const char *TSVConnSslSniGet(TSVConn sslp, int *length); +int TSVConnProvidedSslCert(TSVConn sslp); +const char *TSVConnSslSniGet(TSVConn sslp, int *length); +TSClientHello TSVConnClientHelloGet(TSVConn sslp); TSSslSession TSSslSessionGet(const TSSslSessionID *session_id); int TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr); diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index ada54ed1c98..149a3b2c387 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -53,11 +53,12 @@ static bool create_log_file(); static void register_hooks(); static int handle_client_hello(TSCont cont, TSEvent event, void *edata); static std::string get_fingerprint(SSL *ssl); +static std::string get_fingerprint(SSL_CLIENT_HELLO *ssl); char *get_IP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]); static void log_fingerprint(JA4_data const *data); static std::uint16_t get_version(SSL *ssl); static std::string get_first_ALPN(SSL *ssl); -static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl); +static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *ssl); static void add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl); static std::string hash_with_SHA256(std::string_view sv); static int handle_read_request_hdr(TSCont cont, TSEvent event, void *edata); @@ -163,8 +164,11 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) // We ignore the event, but we don't want to reject the connection. return TS_SUCCESS; } - TSVConn const ssl_vc{static_cast(edata)}; - TSSslConnection const ssl{TSVConnSslConnectionGet(ssl_vc)}; + TSVConn const ssl_vc{static_cast(edata)}; + TSSslConnection const ssl{TSVConnSslConnectionGet(ssl_vc)}; + TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); + const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ssl_client_hello); + if (nullptr == ssl) { Dbg(dbg_ctl, "Could not get SSL object."); } else { @@ -180,14 +184,27 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) } std::string -get_fingerprint(SSL *ssl) +get_fingerprint(SSL_CLIENT_HELLO *ssl) { JA4::TLSClientHelloSummary summary{}; summary.protocol = JA4::Protocol::TLS; - summary.TLS_version = get_version(ssl); - summary.ALPN = get_first_ALPN(ssl); + summary.TLS_version = ssl->; + // summary.ALPN = get_first_ALPN(ssl); add_ciphers(summary, ssl); - add_extensions(summary, ssl); + // add_extensions(summary, ssl); + std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; + return result; +} + +std::string +get_fingerprint(SSL *ssl) +{ + JA4::TLSClientHelloSummary summary{}; + summary.protocol = JA4::Protocol::TLS; + // summary.TLS_version = get_version(ssl); + // summary.ALPN = get_first_ALPN(ssl); + add_ciphers(summary, ssl); + // add_extensions(summary, ssl); std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; return result; } @@ -228,68 +245,88 @@ log_fingerprint(JA4_data const *data) } } -std::uint16_t -get_version(SSL *ssl) -{ - unsigned char const *buf{}; - std::size_t buflen{}; - if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { - std::uint16_t max_version{0}; - for (std::size_t i{1}; i < buflen; i += 2) { - std::uint16_t version{make_word(buf[i - 1], buf[i])}; - if ((!JA4::is_GREASE(version)) && version > max_version) { - max_version = version; - } - } - return max_version; - } else { - Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); - return SSL_client_hello_get0_legacy_version(ssl); - } -} - -std::string -get_first_ALPN(SSL *ssl) -{ - unsigned char const *buf{}; - std::size_t buflen{}; - std::string result{""}; - if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_ALPN, &buf, &buflen)) { - // The first two bytes are a 16bit encoding of the total length. - unsigned char first_ALPN_length{buf[2]}; - TSAssert(buflen > 4); - TSAssert(0 != first_ALPN_length); - result.assign(&buf[3], (&buf[3]) + first_ALPN_length); - } - return result; -} +// std::uint16_t +// get_version(SSL *ssl) +// { +// unsigned char const *buf{}; +// std::size_t buflen{}; +// if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { +// std::uint16_t max_version{0}; +// for (std::size_t i{1}; i < buflen; i += 2) { +// std::uint16_t version{make_word(buf[i - 1], buf[i])}; +// if ((!JA4::is_GREASE(version)) && version > max_version) { +// max_version = version; +// } +// } +// return max_version; +// } else { +// Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); +// return SSL_client_hello_get0_legacy_version(ssl); +// } +// } + +// std::string +// get_first_ALPN(SSL *ssl) +// { +// unsigned char const *buf{}; +// std::size_t buflen{}; +// std::string result{""}; +// if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_ALPN, &buf, &buflen)) { +// // The first two bytes are a 16bit encoding of the total length. +// unsigned char first_ALPN_length{buf[2]}; +// TSAssert(buflen > 4); +// TSAssert(0 != first_ALPN_length); +// result.assign(&buf[3], (&buf[3]) + first_ALPN_length); +// } +// return result; +// } void -add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) +add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) { - unsigned char const *buf{}; - std::size_t buflen{SSL_client_hello_get0_ciphers(ssl, &buf)}; - if (buflen > 0) { - for (std::size_t i{1}; i < buflen; i += 2) { - summary.add_cipher(make_word(buf[i], buf[i - 1])); - } - } else { - Dbg(dbg_ctl, "Failed to get ciphers."); - } -} + const uint8_t *ciphers = client_hello->cipher_suites; + size_t len = client_hello->cipher_suites_len; -void -add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) -{ - int *buf{}; - std::size_t buflen{}; - if (SSL_SUCCESS == SSL_client_hello_get1_extensions_present(ssl, &buf, &buflen)) { - for (std::size_t i{1}; i < buflen; i += 2) { - summary.add_extension(make_word(buf[i], buf[i - 1])); + for (size_t i = 0; i + 1 < len; i += 2) { + uint16_t cipher_value = (ciphers[i] << 8) | ciphers[i + 1]; + summary.add_extension(cipher_value); + + const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_value); + + if (cipher != nullptr) { + const char *cipher_name = SSL_CIPHER_get_name(cipher); + Dbg(dbg_ctl, "0x%04X: %s", cipher_value, cipher_name); + } else { + Dbg(dbg_ctl, " 0x%04X: (unknown/unsupported)", cipher_value); } } - OPENSSL_free(buf); } +// void +// add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) +// { +// unsigned char const *buf{}; +// std::size_t buflen{SSL_client_hello_get0_ciphers(ssl, &buf)}; +// if (buflen > 0) { +// for (std::size_t i{1}; i < buflen; i += 2) { +// summary.add_cipher(make_word(buf[i], buf[i - 1])); +// } +// } else { +// Dbg(dbg_ctl, "Failed to get ciphers."); +// } +// } + +// void +// add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) +// { +// int *buf{}; +// std::size_t buflen{}; +// if (SSL_SUCCESS == SSL_client_hello_get1_extensions_present(ssl, &buf, &buflen)) { +// for (std::size_t i{1}; i < buflen; i += 2) { +// summary.add_extension(make_word(buf[i], buf[i - 1])); +// } +// } +// OPENSSL_free(buf); +// } std::string hash_with_SHA256(std::string_view sv) diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index cabfb5309d6..b63b7e7c2b9 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -7890,6 +7890,23 @@ TSVConnSslSniGet(TSVConn sslp, int *length) return server_name; } +TSClientHello +TSVConnClientHelloGet(TSVConn sslp) +{ + NetVConnection *netvc = reinterpret_cast(sslp); + if (netvc == nullptr) { + return nullptr; + } + + if (auto snis = netvc->get_service(); snis) { + ClientHelloContainer client_hello = snis->get_client_hello_container(); + // Cast the pointer value directly (no const_cast needed if types match) + return reinterpret_cast(const_cast(static_cast(client_hello))); + } + + return nullptr; +} + TSSslVerifyCTX TSVConnSslVerifyCTXGet(TSVConn sslp) { diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc index 0b7bc982ad4..abebf2b9565 100644 --- a/src/iocore/net/SSLUtils.cc +++ b/src/iocore/net/SSLUtils.cc @@ -302,10 +302,12 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO *client_hello) { SSL *s = client_hello->ssl; TLSSNISupport::ClientHello ch = {client_hello}; + #endif TLSSNISupport *snis = TLSSNISupport::getInstance(s); if (snis) { + snis->set_client_hello_container(ch.get_client_hello_container()); snis->on_client_hello(ch); int ret = snis->perform_sni_action(*s); if (ret != SSL_TLSEXT_ERR_OK) { diff --git a/src/iocore/net/TLSSNISupport.cc b/src/iocore/net/TLSSNISupport.cc index ee5e4a8c441..3f5574eb9ee 100644 --- a/src/iocore/net/TLSSNISupport.cc +++ b/src/iocore/net/TLSSNISupport.cc @@ -50,6 +50,25 @@ TLSSNISupport::getInstance(SSL *ssl) return static_cast(SSL_get_ex_data(ssl, _ex_data_index)); } +ClientHelloContainer +TLSSNISupport::ClientHello::get_client_hello_container() +{ + return this->_chc; +} + +// In TLSSNISupport.h +ClientHelloContainer +TLSSNISupport::get_client_hello_container() const +{ + return this->_chc; +} + +void +TLSSNISupport::set_client_hello_container(ClientHelloContainer container) +{ + this->_chc = container; +} + void TLSSNISupport::bind(SSL *ssl, TLSSNISupport *snis) { @@ -98,6 +117,7 @@ TLSSNISupport::on_client_hello(ClientHello &client_hello) const char *servername = nullptr; const unsigned char *p; size_t remaining, len; + // Parse the server name if the get extension call succeeds and there are more than 2 bytes to parse if (client_hello.getExtension(TLSEXT_TYPE_server_name, &p, &remaining) && remaining > 2) { // Parse to get to the name, originally from test/handshake_helper.c in openssl tree From 3f4e5c4915d3befe8053f2ec4c31630ef9ca6eeb Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Wed, 7 Jan 2026 10:35:21 +1100 Subject: [PATCH 02/11] Creates ja4 fingerprint with boringssl --- .../experimental/ja4_fingerprint/plugin.cc | 273 ++++++++++++------ 1 file changed, 183 insertions(+), 90 deletions(-) diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 149a3b2c387..53835532726 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -52,17 +52,24 @@ static void reserve_user_arg(); static bool create_log_file(); static void register_hooks(); static int handle_client_hello(TSCont cont, TSEvent event, void *edata); -static std::string get_fingerprint(SSL *ssl); -static std::string get_fingerprint(SSL_CLIENT_HELLO *ssl); char *get_IP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]); static void log_fingerprint(JA4_data const *data); -static std::uint16_t get_version(SSL *ssl); -static std::string get_first_ALPN(SSL *ssl); -static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *ssl); -static void add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl); -static std::string hash_with_SHA256(std::string_view sv); -static int handle_read_request_hdr(TSCont cont, TSEvent event, void *edata); -static void append_JA4_headers(TSCont cont, TSHttpTxn txnp, std::string const *fingerprint); +#ifdef OPENSSL_IS_BORINGSSL +static std::string get_fingerprint(SSL_CLIENT_HELLO *ssl); +static std::uint16_t get_version(SSL_CLIENT_HELLO *ssl); +static std::string get_first_ALPN(SSL_CLIENT_HELLO *ssl); +static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *ssl); +static void add_extensions(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *ssl); +#else +static std::string get_fingerprint(SSL *ssl); +static std::uint16_t get_version(SSL *ssl); +static std::string get_first_ALPN(SSL *ssl); +static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl); +static void add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl); +#endif +static std::string hash_with_SHA256(std::string_view sv); +static int handle_read_request_hdr(TSCont cont, TSEvent event, void *edata); +static void append_JA4_headers(TSCont cont, TSHttpTxn txnp, std::string const *fingerprint); static void append_to_field(TSMBuffer bufp, TSMLoc hdr_loc, char const *field, int field_len, char const *value, int value_len); static int handle_vconn_close(TSCont cont, TSEvent event, void *edata); @@ -164,11 +171,24 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) // We ignore the event, but we don't want to reject the connection. return TS_SUCCESS; } - TSVConn const ssl_vc{static_cast(edata)}; - TSSslConnection const ssl{TSVConnSslConnectionGet(ssl_vc)}; - TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); - const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ssl_client_hello); + TSVConn const ssl_vc{static_cast(edata)}; + +#ifdef OPENSSL_IS_BORINGSSL + TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); + SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ssl_client_hello); + if (nullptr == client_hello) { + Dbg(dbg_ctl, "Could not get SSL client hello object."); + } else { + auto data{std::make_unique()}; + data->fingerprint = get_fingerprint(client_hello); + get_IP(TSNetVConnRemoteAddrGet(ssl_vc), data->IP_addr); + log_fingerprint(data.get()); + // The VCONN_CLOSE handler is now responsible for freeing the resource. + TSUserArgSet(ssl_vc, *get_user_arg_index(), static_cast(data.release())); + } +#else + TSSslConnection const ssl{TSVConnSslConnectionGet(ssl_vc)}; if (nullptr == ssl) { Dbg(dbg_ctl, "Could not get SSL object."); } else { @@ -179,35 +199,37 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) // The VCONN_CLOSE handler is now responsible for freeing the resource. TSUserArgSet(ssl_vc, *get_user_arg_index(), static_cast(data.release())); } +#endif TSVConnReenable(ssl_vc); return TS_SUCCESS; } - +#ifdef OPENSSL_IS_BORINGSSL std::string get_fingerprint(SSL_CLIENT_HELLO *ssl) { JA4::TLSClientHelloSummary summary{}; summary.protocol = JA4::Protocol::TLS; - summary.TLS_version = ssl->; - // summary.ALPN = get_first_ALPN(ssl); + summary.TLS_version = get_version(ssl); + summary.ALPN = get_first_ALPN(ssl); add_ciphers(summary, ssl); - // add_extensions(summary, ssl); + add_extensions(summary, ssl); std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; return result; } - +#else std::string get_fingerprint(SSL *ssl) { JA4::TLSClientHelloSummary summary{}; - summary.protocol = JA4::Protocol::TLS; - // summary.TLS_version = get_version(ssl); - // summary.ALPN = get_first_ALPN(ssl); + summary.protocol = JA4::Protocol::TLS; + summary.TLS_version = get_version(ssl); + summary.ALPN = get_first_ALPN(ssl); add_ciphers(summary, ssl); - // add_extensions(summary, ssl); + add_extensions(summary, ssl); std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; return result; } +#endif // This implementation is copied verbatim from JA3 fingerprint to make the // potential for deduplication as obvious as possible. @@ -245,42 +267,91 @@ log_fingerprint(JA4_data const *data) } } -// std::uint16_t -// get_version(SSL *ssl) -// { -// unsigned char const *buf{}; -// std::size_t buflen{}; -// if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { -// std::uint16_t max_version{0}; -// for (std::size_t i{1}; i < buflen; i += 2) { -// std::uint16_t version{make_word(buf[i - 1], buf[i])}; -// if ((!JA4::is_GREASE(version)) && version > max_version) { -// max_version = version; -// } -// } -// return max_version; -// } else { -// Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); -// return SSL_client_hello_get0_legacy_version(ssl); -// } -// } - -// std::string -// get_first_ALPN(SSL *ssl) -// { -// unsigned char const *buf{}; -// std::size_t buflen{}; -// std::string result{""}; -// if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_ALPN, &buf, &buflen)) { -// // The first two bytes are a 16bit encoding of the total length. -// unsigned char first_ALPN_length{buf[2]}; -// TSAssert(buflen > 4); -// TSAssert(0 != first_ALPN_length); -// result.assign(&buf[3], (&buf[3]) + first_ALPN_length); -// } -// return result; -// } +#ifdef OPENSSL_IS_BORINGSSL +std::uint16_t +get_version(SSL_CLIENT_HELLO *client_hello) +{ + unsigned char const *buf{}; + std::size_t buflen{}; + // If no extensions, fall back to legacy version field + if (!client_hello->extensions || client_hello->extensions_len == 0) { + return client_hello->version; + } + + if (SSL_SUCCESS == SSL_early_callback_ctx_extension_get(client_hello, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { + std::uint16_t max_version{0}; + uint8_t list_len = buf[0]; + for (size_t i = 1; i + 1 < buflen && i < list_len + 1; i += 2) { + std::uint16_t version = (buf[i] << 8) | buf[i + 1]; + if (!JA4::is_GREASE(version) && version > max_version) { + max_version = version; + } + } + return max_version; + } else { + Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); + return client_hello->version; + } +} +#else +std::uint16_t +get_version(SSL *ssl) +{ + unsigned char const *buf{}; + std::size_t buflen{}; + if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { + std::uint16_t max_version{0}; + for (std::size_t i{1}; i < buflen; i += 2) { + std::uint16_t version{make_word(buf[i - 1], buf[i])}; + if ((!JA4::is_GREASE(version)) && version > max_version) { + max_version = version; + } + } + return max_version; + } else { + Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); + return SSL_client_hello_get0_legacy_version(ssl); + } +} +#endif + +#ifdef OPENSSL_IS_BORINGSSL +std::string +get_first_ALPN(SSL_CLIENT_HELLO *client_hello) +{ + const uint8_t *buf = nullptr; + size_t buflen = 0; + std::string result; + + bool found = SSL_early_callback_ctx_extension_get(client_hello, EXT_ALPN, &buf, &buflen); + if (found && buflen > 3) { + uint8_t first_ALPN_length = buf[2]; + if (first_ALPN_length > 0 && 3 + first_ALPN_length <= buflen) { + result.assign(reinterpret_cast(&buf[3]), first_ALPN_length); + } + } + + return result; +} +#else +std::string +get_first_ALPN(SSL *ssl) +{ + unsigned char const *buf{}; + std::size_t buflen{}; + std::string result{""}; + if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_ALPN, &buf, &buflen)) { + // The first two bytes are a 16bit encoding of the total length. + unsigned char first_ALPN_length{buf[2]}; + TSAssert(buflen > 4); + TSAssert(0 != first_ALPN_length); + result.assign(&buf[3], (&buf[3]) + first_ALPN_length); + } + return result; +} +#endif +#ifdef OPENSSL_IS_BORINGSSL void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) { @@ -289,45 +360,67 @@ add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) for (size_t i = 0; i + 1 < len; i += 2) { uint16_t cipher_value = (ciphers[i] << 8) | ciphers[i + 1]; - summary.add_extension(cipher_value); + summary.add_cipher(cipher_value); + } +} +#else +void +add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) +{ + unsigned char const *buf{}; + std::size_t buflen{SSL_client_hello_get0_ciphers(ssl, &buf)}; + if (buflen > 0) { + for (std::size_t i{1}; i < buflen; i += 2) { + summary.add_cipher(make_word(buf[i], buf[i - 1])); + } + } else { + Dbg(dbg_ctl, "Failed to get ciphers."); + } +} +#endif + +#ifdef OPENSSL_IS_BORINGSSL +void +add_extensions(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) +{ + const uint8_t *ext = client_hello->extensions; + size_t remaining = client_hello->extensions_len; + + while (remaining >= 4) { // Need at least 4 bytes (2 type + 2 length) + // Read extension type (2 bytes, big endian) + uint16_t ext_type = (ext[0] << 8) | ext[1]; - const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_value); + // Read extension length (2 bytes, big endian) + uint16_t ext_len = (ext[2] << 8) | ext[3]; - if (cipher != nullptr) { - const char *cipher_name = SSL_CIPHER_get_name(cipher); - Dbg(dbg_ctl, "0x%04X: %s", cipher_value, cipher_name); - } else { - Dbg(dbg_ctl, " 0x%04X: (unknown/unsupported)", cipher_value); + // Add the extension type to summary + summary.add_extension(ext_type); + + // Move to next extension + size_t total_ext_size = 4 + ext_len; // 4 bytes header + data + if (total_ext_size > remaining) { + break; // Malformed extension, stop parsing } + + ext += total_ext_size; + remaining -= total_ext_size; } } -// void -// add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) -// { -// unsigned char const *buf{}; -// std::size_t buflen{SSL_client_hello_get0_ciphers(ssl, &buf)}; -// if (buflen > 0) { -// for (std::size_t i{1}; i < buflen; i += 2) { -// summary.add_cipher(make_word(buf[i], buf[i - 1])); -// } -// } else { -// Dbg(dbg_ctl, "Failed to get ciphers."); -// } -// } - -// void -// add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) -// { -// int *buf{}; -// std::size_t buflen{}; -// if (SSL_SUCCESS == SSL_client_hello_get1_extensions_present(ssl, &buf, &buflen)) { -// for (std::size_t i{1}; i < buflen; i += 2) { -// summary.add_extension(make_word(buf[i], buf[i - 1])); -// } -// } -// OPENSSL_free(buf); -// } +#else +void +add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) +{ + int *buf{}; + std::size_t buflen{}; + if (SSL_SUCCESS == SSL_client_hello_get1_extensions_present(ssl, &buf, &buflen)) { + for (std::size_t i{1}; i < buflen; i += 2) { + summary.add_extension(make_word(buf[i], buf[i - 1])); + } + } + OPENSSL_free(buf); +} +#endif std::string hash_with_SHA256(std::string_view sv) { From 5c8c8ed5aa53e160b04e3700badadb93538e1bcf Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Wed, 7 Jan 2026 11:46:37 +1100 Subject: [PATCH 03/11] cleanup a bit --- .../experimental/ja4_fingerprint/plugin.cc | 116 +++++------------- 1 file changed, 33 insertions(+), 83 deletions(-) diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 53835532726..4adeda4ae6b 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -176,49 +176,32 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) #ifdef OPENSSL_IS_BORINGSSL TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); - SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ssl_client_hello); - if (nullptr == client_hello) { - Dbg(dbg_ctl, "Could not get SSL client hello object."); - } else { - auto data{std::make_unique()}; - data->fingerprint = get_fingerprint(client_hello); - get_IP(TSNetVConnRemoteAddrGet(ssl_vc), data->IP_addr); - log_fingerprint(data.get()); - // The VCONN_CLOSE handler is now responsible for freeing the resource. - TSUserArgSet(ssl_vc, *get_user_arg_index(), static_cast(data.release())); - } + SSL_CLIENT_HELLO *ssl = reinterpret_cast(ssl_client_hello); #else - TSSslConnection const ssl{TSVConnSslConnectionGet(ssl_vc)}; + TSSslConnection const ssl_conn{TSVConnSslConnectionGet(ssl_vc)}; + SSL *ssl = reinterpret_cast(ssl_conn); +#endif if (nullptr == ssl) { - Dbg(dbg_ctl, "Could not get SSL object."); + Dbg(dbg_ctl, "Could not get SSL client hello object."); } else { auto data{std::make_unique()}; - data->fingerprint = get_fingerprint(reinterpret_cast(ssl)); + data->fingerprint = get_fingerprint(ssl); get_IP(TSNetVConnRemoteAddrGet(ssl_vc), data->IP_addr); log_fingerprint(data.get()); // The VCONN_CLOSE handler is now responsible for freeing the resource. TSUserArgSet(ssl_vc, *get_user_arg_index(), static_cast(data.release())); } -#endif + TSVConnReenable(ssl_vc); return TS_SUCCESS; } -#ifdef OPENSSL_IS_BORINGSSL + std::string +#ifdef OPENSSL_IS_BORINGSSL get_fingerprint(SSL_CLIENT_HELLO *ssl) -{ - JA4::TLSClientHelloSummary summary{}; - summary.protocol = JA4::Protocol::TLS; - summary.TLS_version = get_version(ssl); - summary.ALPN = get_first_ALPN(ssl); - add_ciphers(summary, ssl); - add_extensions(summary, ssl); - std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; - return result; -} #else -std::string get_fingerprint(SSL *ssl) +#endif { JA4::TLSClientHelloSummary summary{}; summary.protocol = JA4::Protocol::TLS; @@ -229,7 +212,6 @@ get_fingerprint(SSL *ssl) std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; return result; } -#endif // This implementation is copied verbatim from JA3 fingerprint to make the // potential for deduplication as obvious as possible. @@ -267,18 +249,24 @@ log_fingerprint(JA4_data const *data) } } -#ifdef OPENSSL_IS_BORINGSSL std::uint16_t +#ifdef OPENSSL_IS_BORINGSSL get_version(SSL_CLIENT_HELLO *client_hello) +#else +get_version(SSL *ssl) +#endif { unsigned char const *buf{}; std::size_t buflen{}; +#ifdef OPENSSL_IS_BORINGSSL // If no extensions, fall back to legacy version field if (!client_hello->extensions || client_hello->extensions_len == 0) { return client_hello->version; } - if (SSL_SUCCESS == SSL_early_callback_ctx_extension_get(client_hello, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { +#else + if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { +#endif std::uint16_t max_version{0}; uint8_t list_len = buf[0]; for (size_t i = 1; i + 1 < buflen && i < list_len + 1; i += 2) { @@ -290,85 +278,53 @@ get_version(SSL_CLIENT_HELLO *client_hello) return max_version; } else { Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); +#ifdef OPENSSL_IS_BORINGSSL return client_hello->version; - } -} #else -std::uint16_t -get_version(SSL *ssl) -{ - unsigned char const *buf{}; - std::size_t buflen{}; - if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { - std::uint16_t max_version{0}; - for (std::size_t i{1}; i < buflen; i += 2) { - std::uint16_t version{make_word(buf[i - 1], buf[i])}; - if ((!JA4::is_GREASE(version)) && version > max_version) { - max_version = version; - } - } - return max_version; - } else { - Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); return SSL_client_hello_get0_legacy_version(ssl); +#endif } } -#endif -#ifdef OPENSSL_IS_BORINGSSL std::string +#ifdef OPENSSL_IS_BORINGSSL get_first_ALPN(SSL_CLIENT_HELLO *client_hello) -{ - const uint8_t *buf = nullptr; - size_t buflen = 0; - std::string result; - - bool found = SSL_early_callback_ctx_extension_get(client_hello, EXT_ALPN, &buf, &buflen); - if (found && buflen > 3) { - uint8_t first_ALPN_length = buf[2]; - if (first_ALPN_length > 0 && 3 + first_ALPN_length <= buflen) { - result.assign(reinterpret_cast(&buf[3]), first_ALPN_length); - } - } - - return result; -} #else -std::string get_first_ALPN(SSL *ssl) +#endif { unsigned char const *buf{}; std::size_t buflen{}; std::string result{""}; +#ifdef OPENSSL_IS_BORINGSSL + if (SSL_SUCCESS == SSL_early_callback_ctx_extension_get(client_hello, EXT_ALPN, &buf, &buflen)) { +#else if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_ALPN, &buf, &buflen)) { +#endif // The first two bytes are a 16bit encoding of the total length. unsigned char first_ALPN_length{buf[2]}; TSAssert(buflen > 4); TSAssert(0 != first_ALPN_length); result.assign(&buf[3], (&buf[3]) + first_ALPN_length); } + return result; } -#endif #ifdef OPENSSL_IS_BORINGSSL void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) { - const uint8_t *ciphers = client_hello->cipher_suites; - size_t len = client_hello->cipher_suites_len; - - for (size_t i = 0; i + 1 < len; i += 2) { - uint16_t cipher_value = (ciphers[i] << 8) | ciphers[i + 1]; - summary.add_cipher(cipher_value); - } -} + const uint8_t *buf = client_hello->cipher_suites; + size_t buflen = client_hello->cipher_suites_len; #else void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) { unsigned char const *buf{}; std::size_t buflen{SSL_client_hello_get0_ciphers(ssl, &buf)}; +#endif + if (buflen > 0) { for (std::size_t i{1}; i < buflen; i += 2) { summary.add_cipher(make_word(buf[i], buf[i - 1])); @@ -377,7 +333,6 @@ add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) Dbg(dbg_ctl, "Failed to get ciphers."); } } -#endif #ifdef OPENSSL_IS_BORINGSSL void @@ -386,17 +341,12 @@ add_extensions(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hel const uint8_t *ext = client_hello->extensions; size_t remaining = client_hello->extensions_len; - while (remaining >= 4) { // Need at least 4 bytes (2 type + 2 length) - // Read extension type (2 bytes, big endian) + while (remaining >= 4) { uint16_t ext_type = (ext[0] << 8) | ext[1]; // Read extension length (2 bytes, big endian) uint16_t ext_len = (ext[2] << 8) | ext[3]; - - // Add the extension type to summary summary.add_extension(ext_type); - - // Move to next extension size_t total_ext_size = 4 + ext_len; // 4 bytes header + data if (total_ext_size > remaining) { break; // Malformed extension, stop parsing @@ -406,7 +356,6 @@ add_extensions(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hel remaining -= total_ext_size; } } - #else void add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) @@ -421,6 +370,7 @@ add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) OPENSSL_free(buf); } #endif + std::string hash_with_SHA256(std::string_view sv) { From 0efeade3015b25ef7cebcbd891caf2ffc0a03d26 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Wed, 7 Jan 2026 12:35:38 +1100 Subject: [PATCH 04/11] make ssl_client_hello const --- include/ts/apidefs.h.in | 2 +- .../experimental/ja4_fingerprint/plugin.cc | 26 +++++++++---------- src/api/InkAPI.cc | 3 +-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index dda12fa0db6..32f7221a477 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1080,7 +1080,7 @@ using TSHostLookupResult = struct tsapi_hostlookupresult *; using TSAIOCallback = struct tsapi_aiocallback *; using TSAcceptor = struct tsapi_net_accept *; using TSRemapPluginInfo = struct tsapi_remap_plugin_info *; -using TSClientHello = struct tsapi_clienthello *; +using TSClientHello = const void *; using TSFetchSM = struct tsapi_fetchsm *; diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 4adeda4ae6b..058eefd98fe 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -55,11 +55,11 @@ static int handle_client_hello(TSCont cont, TSEvent event, void * char *get_IP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]); static void log_fingerprint(JA4_data const *data); #ifdef OPENSSL_IS_BORINGSSL -static std::string get_fingerprint(SSL_CLIENT_HELLO *ssl); -static std::uint16_t get_version(SSL_CLIENT_HELLO *ssl); -static std::string get_first_ALPN(SSL_CLIENT_HELLO *ssl); -static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *ssl); -static void add_extensions(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *ssl); +static std::string get_fingerprint(const SSL_CLIENT_HELLO *ssl); +static std::uint16_t get_version(const SSL_CLIENT_HELLO *ssl); +static std::string get_first_ALPN(const SSL_CLIENT_HELLO *ssl); +static void add_ciphers(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *ssl); +static void add_extensions(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *ssl); #else static std::string get_fingerprint(SSL *ssl); static std::uint16_t get_version(SSL *ssl); @@ -175,14 +175,14 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) TSVConn const ssl_vc{static_cast(edata)}; #ifdef OPENSSL_IS_BORINGSSL - TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); - SSL_CLIENT_HELLO *ssl = reinterpret_cast(ssl_client_hello); + TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); + const SSL_CLIENT_HELLO *ssl = reinterpret_cast(ssl_client_hello); #else TSSslConnection const ssl_conn{TSVConnSslConnectionGet(ssl_vc)}; SSL *ssl = reinterpret_cast(ssl_conn); #endif if (nullptr == ssl) { - Dbg(dbg_ctl, "Could not get SSL client hello object."); + Dbg(dbg_ctl, "Could not get SSL object."); } else { auto data{std::make_unique()}; data->fingerprint = get_fingerprint(ssl); @@ -198,7 +198,7 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) std::string #ifdef OPENSSL_IS_BORINGSSL -get_fingerprint(SSL_CLIENT_HELLO *ssl) +get_fingerprint(const SSL_CLIENT_HELLO *ssl) #else get_fingerprint(SSL *ssl) #endif @@ -251,7 +251,7 @@ log_fingerprint(JA4_data const *data) std::uint16_t #ifdef OPENSSL_IS_BORINGSSL -get_version(SSL_CLIENT_HELLO *client_hello) +get_version(const SSL_CLIENT_HELLO *client_hello) #else get_version(SSL *ssl) #endif @@ -288,7 +288,7 @@ get_version(SSL *ssl) std::string #ifdef OPENSSL_IS_BORINGSSL -get_first_ALPN(SSL_CLIENT_HELLO *client_hello) +get_first_ALPN(const SSL_CLIENT_HELLO *client_hello) #else get_first_ALPN(SSL *ssl) #endif @@ -313,7 +313,7 @@ get_first_ALPN(SSL *ssl) #ifdef OPENSSL_IS_BORINGSSL void -add_ciphers(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) +add_ciphers(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *client_hello) { const uint8_t *buf = client_hello->cipher_suites; size_t buflen = client_hello->cipher_suites_len; @@ -336,7 +336,7 @@ add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) #ifdef OPENSSL_IS_BORINGSSL void -add_extensions(JA4::TLSClientHelloSummary &summary, SSL_CLIENT_HELLO *client_hello) +add_extensions(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *client_hello) { const uint8_t *ext = client_hello->extensions; size_t remaining = client_hello->extensions_len; diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index b63b7e7c2b9..f703e3607ab 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -7900,8 +7900,7 @@ TSVConnClientHelloGet(TSVConn sslp) if (auto snis = netvc->get_service(); snis) { ClientHelloContainer client_hello = snis->get_client_hello_container(); - // Cast the pointer value directly (no const_cast needed if types match) - return reinterpret_cast(const_cast(static_cast(client_hello))); + return reinterpret_cast(client_hello); } return nullptr; From 9765eb838b056b77b1907220e7561e0ec880ecf7 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Wed, 7 Jan 2026 12:39:50 +1100 Subject: [PATCH 05/11] spaces cleanup --- cmake/ExperimentalPlugins.cmake | 6 +----- plugins/experimental/ja4_fingerprint/README.md | 2 ++ plugins/experimental/ja4_fingerprint/plugin.cc | 1 - src/iocore/net/SSLUtils.cc | 1 - src/iocore/net/TLSSNISupport.cc | 1 - 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/cmake/ExperimentalPlugins.cmake b/cmake/ExperimentalPlugins.cmake index 74347efde92..c546e7ebb5f 100644 --- a/cmake/ExperimentalPlugins.cmake +++ b/cmake/ExperimentalPlugins.cmake @@ -41,11 +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 diff --git a/plugins/experimental/ja4_fingerprint/README.md b/plugins/experimental/ja4_fingerprint/README.md index d45ddf00785..b1b4dd55c7d 100644 --- a/plugins/experimental/ja4_fingerprint/README.md +++ b/plugins/experimental/ja4_fingerprint/README.md @@ -21,6 +21,8 @@ The technical specification of the algorithm is available [here](https://github. These changes were made to simplify the plugin as much as possible. The missing features are useful and may be implemented in the future. +Ja4 now supports boringssl + ## Logging and Debugging To get debug information in the traffic log, enable the debug tag `ja4_fingerprint`. diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 058eefd98fe..f0ef4aaa5ff 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -191,7 +191,6 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) // The VCONN_CLOSE handler is now responsible for freeing the resource. TSUserArgSet(ssl_vc, *get_user_arg_index(), static_cast(data.release())); } - TSVConnReenable(ssl_vc); return TS_SUCCESS; } diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc index abebf2b9565..ede8f20543f 100644 --- a/src/iocore/net/SSLUtils.cc +++ b/src/iocore/net/SSLUtils.cc @@ -302,7 +302,6 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO *client_hello) { SSL *s = client_hello->ssl; TLSSNISupport::ClientHello ch = {client_hello}; - #endif TLSSNISupport *snis = TLSSNISupport::getInstance(s); diff --git a/src/iocore/net/TLSSNISupport.cc b/src/iocore/net/TLSSNISupport.cc index 3f5574eb9ee..b4ced5d632a 100644 --- a/src/iocore/net/TLSSNISupport.cc +++ b/src/iocore/net/TLSSNISupport.cc @@ -117,7 +117,6 @@ TLSSNISupport::on_client_hello(ClientHello &client_hello) const char *servername = nullptr; const unsigned char *p; size_t remaining, len; - // Parse the server name if the get extension call succeeds and there are more than 2 bytes to parse if (client_hello.getExtension(TLSEXT_TYPE_server_name, &p, &remaining) && remaining > 2) { // Parse to get to the name, originally from test/handshake_helper.c in openssl tree From 2bc38d078ea56105161be70a8de5ad0d4668913a Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Wed, 7 Jan 2026 15:37:16 +1100 Subject: [PATCH 06/11] cleanup code --- include/ts/ts.h | 10 +- .../experimental/ja4_fingerprint/plugin.cc | 121 ++++++------------ src/api/InkAPI.cc | 24 ++++ 3 files changed, 71 insertions(+), 84 deletions(-) diff --git a/include/ts/ts.h b/include/ts/ts.h index 892d58a0c6f..c2b42435679 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -1334,11 +1334,11 @@ int TSVConnIsSsl(TSVConn sslp); int TSVConnProvidedSslCert(TSVConn sslp); const char *TSVConnSslSniGet(TSVConn sslp, int *length); TSClientHello TSVConnClientHelloGet(TSVConn sslp); - -TSSslSession TSSslSessionGet(const TSSslSessionID *session_id); -int TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr); -TSReturnCode TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session, TSSslConnection ssl_conn); -TSReturnCode TSSslSessionRemove(const TSSslSessionID *session_id); +TSReturnCode TSVConnClientHelloExtGet(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen); +TSSslSession TSSslSessionGet(const TSSslSessionID *session_id); +int TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr); +TSReturnCode TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session, TSSslConnection ssl_conn); +TSReturnCode TSSslSessionRemove(const TSSslSessionID *session_id); /* -------------------------------------------------------------------------- HTTP transactions */ diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index f0ef4aaa5ff..ac37757b28b 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -54,22 +54,14 @@ static void register_hooks(); static int handle_client_hello(TSCont cont, TSEvent event, void *edata); char *get_IP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]); static void log_fingerprint(JA4_data const *data); -#ifdef OPENSSL_IS_BORINGSSL -static std::string get_fingerprint(const SSL_CLIENT_HELLO *ssl); -static std::uint16_t get_version(const SSL_CLIENT_HELLO *ssl); -static std::string get_first_ALPN(const SSL_CLIENT_HELLO *ssl); -static void add_ciphers(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *ssl); -static void add_extensions(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *ssl); -#else -static std::string get_fingerprint(SSL *ssl); -static std::uint16_t get_version(SSL *ssl); -static std::string get_first_ALPN(SSL *ssl); -static void add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl); -static void add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl); -#endif -static std::string hash_with_SHA256(std::string_view sv); -static int handle_read_request_hdr(TSCont cont, TSEvent event, void *edata); -static void append_JA4_headers(TSCont cont, TSHttpTxn txnp, std::string const *fingerprint); +static std::string get_fingerprint(TSClientHello ch); +static std::uint16_t get_version(TSClientHello ch); +static std::string get_first_ALPN(TSClientHello ch); +static void add_ciphers(JA4::TLSClientHelloSummary &summary, TSClientHello ch); +static void add_extensions(JA4::TLSClientHelloSummary &summary, TSClientHello ch); +static std::string hash_with_SHA256(std::string_view sv); +static int handle_read_request_hdr(TSCont cont, TSEvent event, void *edata); +static void append_JA4_headers(TSCont cont, TSHttpTxn txnp, std::string const *fingerprint); static void append_to_field(TSMBuffer bufp, TSMLoc hdr_loc, char const *field, int field_len, char const *value, int value_len); static int handle_vconn_close(TSCont cont, TSEvent event, void *edata); @@ -83,7 +75,6 @@ constexpr std::string_view JA4_VIA_HEADER{"x-ja4-via"}; constexpr unsigned int EXT_ALPN{0x10}; constexpr unsigned int EXT_SUPPORTED_VERSIONS{0x2b}; -constexpr int SSL_SUCCESS{1}; DbgCtl dbg_ctl{PLUGIN_NAME}; @@ -175,17 +166,16 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) TSVConn const ssl_vc{static_cast(edata)}; #ifdef OPENSSL_IS_BORINGSSL - TSClientHello ssl_client_hello = TSVConnClientHelloGet(ssl_vc); - const SSL_CLIENT_HELLO *ssl = reinterpret_cast(ssl_client_hello); + TSClientHello ch = TSVConnClientHelloGet(ssl_vc); #else TSSslConnection const ssl_conn{TSVConnSslConnectionGet(ssl_vc)}; - SSL *ssl = reinterpret_cast(ssl_conn); + TSClientHello ch = reinterpret_cast(ssl_conn); #endif - if (nullptr == ssl) { - Dbg(dbg_ctl, "Could not get SSL object."); + if (nullptr == ch) { + Dbg(dbg_ctl, "Could not get TSClientHello object."); } else { auto data{std::make_unique()}; - data->fingerprint = get_fingerprint(ssl); + data->fingerprint = get_fingerprint(ch); get_IP(TSNetVConnRemoteAddrGet(ssl_vc), data->IP_addr); log_fingerprint(data.get()); // The VCONN_CLOSE handler is now responsible for freeing the resource. @@ -196,18 +186,14 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) } std::string -#ifdef OPENSSL_IS_BORINGSSL -get_fingerprint(const SSL_CLIENT_HELLO *ssl) -#else -get_fingerprint(SSL *ssl) -#endif +get_fingerprint(TSClientHello ch) { JA4::TLSClientHelloSummary summary{}; summary.protocol = JA4::Protocol::TLS; - summary.TLS_version = get_version(ssl); - summary.ALPN = get_first_ALPN(ssl); - add_ciphers(summary, ssl); - add_extensions(summary, ssl); + summary.TLS_version = get_version(ch); + summary.ALPN = get_first_ALPN(ch); + add_ciphers(summary, ch); + add_extensions(summary, ch); std::string result{JA4::make_JA4_fingerprint(summary, hash_with_SHA256)}; return result; } @@ -249,23 +235,11 @@ log_fingerprint(JA4_data const *data) } std::uint16_t -#ifdef OPENSSL_IS_BORINGSSL -get_version(const SSL_CLIENT_HELLO *client_hello) -#else -get_version(SSL *ssl) -#endif +get_version(TSClientHello ch) { unsigned char const *buf{}; std::size_t buflen{}; -#ifdef OPENSSL_IS_BORINGSSL - // If no extensions, fall back to legacy version field - if (!client_hello->extensions || client_hello->extensions_len == 0) { - return client_hello->version; - } - if (SSL_SUCCESS == SSL_early_callback_ctx_extension_get(client_hello, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { -#else - if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { -#endif + if (TS_SUCCESS == TSVConnClientHelloExtGet(ch, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { std::uint16_t max_version{0}; uint8_t list_len = buf[0]; for (size_t i = 1; i + 1 < buflen && i < list_len + 1; i += 2) { @@ -278,28 +252,20 @@ get_version(SSL *ssl) } else { Dbg(dbg_ctl, "No supported_versions extension... using legacy version."); #ifdef OPENSSL_IS_BORINGSSL - return client_hello->version; + return reinterpret_cast(ch)->version; #else - return SSL_client_hello_get0_legacy_version(ssl); + return SSL_client_hello_get0_legacy_version(reinterpret_cast(ch)); #endif } } std::string -#ifdef OPENSSL_IS_BORINGSSL -get_first_ALPN(const SSL_CLIENT_HELLO *client_hello) -#else -get_first_ALPN(SSL *ssl) -#endif +get_first_ALPN(TSClientHello ch) { unsigned char const *buf{}; std::size_t buflen{}; std::string result{""}; -#ifdef OPENSSL_IS_BORINGSSL - if (SSL_SUCCESS == SSL_early_callback_ctx_extension_get(client_hello, EXT_ALPN, &buf, &buflen)) { -#else - if (SSL_SUCCESS == SSL_client_hello_get0_ext(ssl, EXT_ALPN, &buf, &buflen)) { -#endif + if (TS_SUCCESS == TSVConnClientHelloExtGet(ch, EXT_ALPN, &buf, &buflen)) { // The first two bytes are a 16bit encoding of the total length. unsigned char first_ALPN_length{buf[2]}; TSAssert(buflen > 4); @@ -310,35 +276,36 @@ get_first_ALPN(SSL *ssl) return result; } -#ifdef OPENSSL_IS_BORINGSSL void -add_ciphers(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *client_hello) +add_ciphers(JA4::TLSClientHelloSummary &summary, TSClientHello ch) { - const uint8_t *buf = client_hello->cipher_suites; - size_t buflen = client_hello->cipher_suites_len; +#ifdef OPENSSL_IS_BORINGSSL + const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ch); + const uint8_t *buf = client_hello->cipher_suites; + size_t buflen = client_hello->cipher_suites_len; #else -void -add_ciphers(JA4::TLSClientHelloSummary &summary, SSL *ssl) -{ - unsigned char const *buf{}; - std::size_t buflen{SSL_client_hello_get0_ciphers(ssl, &buf)}; + unsigned char const *buf = nullptr; + // Fix: Add const_cast to remove const from ch + SSL *ssl = const_cast(reinterpret_cast(ch)); + std::size_t buflen = SSL_client_hello_get0_ciphers(ssl, &buf); #endif if (buflen > 0) { - for (std::size_t i{1}; i < buflen; i += 2) { - summary.add_cipher(make_word(buf[i], buf[i - 1])); + for (std::size_t i = 0; i + 1 < buflen; i += 2) { + summary.add_cipher(make_word(buf[i], buf[i + 1])); } } else { Dbg(dbg_ctl, "Failed to get ciphers."); } } -#ifdef OPENSSL_IS_BORINGSSL void -add_extensions(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *client_hello) +add_extensions(JA4::TLSClientHelloSummary &summary, TSClientHello ch) { - const uint8_t *ext = client_hello->extensions; - size_t remaining = client_hello->extensions_len; +#ifdef OPENSSL_IS_BORINGSSL + const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ch); + const uint8_t *ext = client_hello->extensions; + size_t remaining = client_hello->extensions_len; while (remaining >= 4) { uint16_t ext_type = (ext[0] << 8) | ext[1]; @@ -354,21 +321,17 @@ add_extensions(JA4::TLSClientHelloSummary &summary, const SSL_CLIENT_HELLO *clie ext += total_ext_size; remaining -= total_ext_size; } -} #else -void -add_extensions(JA4::TLSClientHelloSummary &summary, SSL *ssl) -{ int *buf{}; std::size_t buflen{}; - if (SSL_SUCCESS == SSL_client_hello_get1_extensions_present(ssl, &buf, &buflen)) { + if (1 == SSL_client_hello_get1_extensions_present(reinterpret_cast(ch), &buf, &buflen)) { for (std::size_t i{1}; i < buflen; i += 2) { summary.add_extension(make_word(buf[i], buf[i - 1])); } } OPENSSL_free(buf); -} #endif +} std::string hash_with_SHA256(std::string_view sv) diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index f703e3607ab..c5e15145265 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -7906,6 +7906,30 @@ TSVConnClientHelloGet(TSVConn sslp) return nullptr; } +TSReturnCode +TSVConnClientHelloExtGet(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen) +{ + TSReturnCode retval = TS_SUCCESS; + + if (ch == nullptr) { + return TS_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ch); + if (SSL_early_callback_ctx_extension_get(client_hello, type, out, outlen) == 1) { + return TS_SUCCESS; + } +#else + SSL *ssl = const_cast(reinterpret_cast(ch)); + if (SSL_client_hello_get0_ext(ssl, type, out, outlen) == 1) { + return TS_SUCCESS; + } +#endif + + return retval; +} + TSSslVerifyCTX TSVConnSslVerifyCTXGet(TSVConn sslp) { From fd0380651f9a69c53ec6c1016e11e128d9ebad37 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Thu, 8 Jan 2026 11:41:56 +1100 Subject: [PATCH 07/11] more cleanup --- include/ts/apidefs.h.in | 2 +- .../experimental/ja4_fingerprint/plugin.cc | 34 ++++++++++++++++--- src/api/InkAPI.cc | 26 ++------------ 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 32f7221a477..aac11c2f68d 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1080,7 +1080,7 @@ using TSHostLookupResult = struct tsapi_hostlookupresult *; using TSAIOCallback = struct tsapi_aiocallback *; using TSAcceptor = struct tsapi_net_accept *; using TSRemapPluginInfo = struct tsapi_remap_plugin_info *; -using TSClientHello = const void *; +using TSClientHello = void *; using TSFetchSM = struct tsapi_fetchsm *; diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index ac37757b28b..3c5d0efaca1 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -64,6 +64,7 @@ static int handle_read_request_hdr(TSCont cont, TSEvent event, vo static void append_JA4_headers(TSCont cont, TSHttpTxn txnp, std::string const *fingerprint); static void append_to_field(TSMBuffer bufp, TSMLoc hdr_loc, char const *field, int field_len, char const *value, int value_len); static int handle_vconn_close(TSCont cont, TSEvent event, void *edata); +int client_hello_ext_get(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen); namespace { @@ -171,6 +172,7 @@ handle_client_hello(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) TSSslConnection const ssl_conn{TSVConnSslConnectionGet(ssl_vc)}; TSClientHello ch = reinterpret_cast(ssl_conn); #endif + if (nullptr == ch) { Dbg(dbg_ctl, "Could not get TSClientHello object."); } else { @@ -239,7 +241,7 @@ get_version(TSClientHello ch) { unsigned char const *buf{}; std::size_t buflen{}; - if (TS_SUCCESS == TSVConnClientHelloExtGet(ch, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { + if (TS_SUCCESS == client_hello_ext_get(ch, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { std::uint16_t max_version{0}; uint8_t list_len = buf[0]; for (size_t i = 1; i + 1 < buflen && i < list_len + 1; i += 2) { @@ -265,7 +267,7 @@ get_first_ALPN(TSClientHello ch) unsigned char const *buf{}; std::size_t buflen{}; std::string result{""}; - if (TS_SUCCESS == TSVConnClientHelloExtGet(ch, EXT_ALPN, &buf, &buflen)) { + if (TS_SUCCESS == client_hello_ext_get(ch, EXT_ALPN, &buf, &buflen)) { // The first two bytes are a 16bit encoding of the total length. unsigned char first_ALPN_length{buf[2]}; TSAssert(buflen > 4); @@ -286,7 +288,7 @@ add_ciphers(JA4::TLSClientHelloSummary &summary, TSClientHello ch) #else unsigned char const *buf = nullptr; // Fix: Add const_cast to remove const from ch - SSL *ssl = const_cast(reinterpret_cast(ch)); + SSL *ssl = const_cast(reinterpret_cast(ch)); std::size_t buflen = SSL_client_hello_get0_ciphers(ssl, &buf); #endif @@ -324,7 +326,7 @@ add_extensions(JA4::TLSClientHelloSummary &summary, TSClientHello ch) #else int *buf{}; std::size_t buflen{}; - if (1 == SSL_client_hello_get1_extensions_present(reinterpret_cast(ch), &buf, &buflen)) { + if (1 == SSL_client_hello_get1_extensions_present(const_cast(reinterpret_cast(ch)), &buf, &buflen)) { for (std::size_t i{1}; i < buflen; i += 2) { summary.add_extension(make_word(buf[i], buf[i - 1])); } @@ -436,3 +438,27 @@ handle_vconn_close(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) TSVConnReenable(ssl_vc); return TS_SUCCESS; } + +int +client_hello_ext_get(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen) +{ + TSReturnCode retval = TS_SUCCESS; + + if (ch == nullptr) { + return TS_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ch); + if (SSL_early_callback_ctx_extension_get(client_hello, type, out, outlen) == 1) { + return TS_SUCCESS; + } +#else + SSL *ssl = const_cast(reinterpret_cast(ch)); + if (SSL_client_hello_get0_ext(ssl, type, out, outlen) == 1) { + return TS_SUCCESS; + } +#endif + + return retval; +} diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index c5e15145265..b55ad7401df 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -7890,6 +7890,7 @@ TSVConnSslSniGet(TSVConn sslp, int *length) return server_name; } +#ifdef OPENSSL_IS_BORINGSSL TSClientHello TSVConnClientHelloGet(TSVConn sslp) { @@ -7900,36 +7901,13 @@ TSVConnClientHelloGet(TSVConn sslp) if (auto snis = netvc->get_service(); snis) { ClientHelloContainer client_hello = snis->get_client_hello_container(); - return reinterpret_cast(client_hello); + return reinterpret_cast(const_cast(client_hello)); } return nullptr; } - -TSReturnCode -TSVConnClientHelloExtGet(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen) -{ - TSReturnCode retval = TS_SUCCESS; - - if (ch == nullptr) { - return TS_ERROR; - } - -#ifdef OPENSSL_IS_BORINGSSL - const SSL_CLIENT_HELLO *client_hello = reinterpret_cast(ch); - if (SSL_early_callback_ctx_extension_get(client_hello, type, out, outlen) == 1) { - return TS_SUCCESS; - } -#else - SSL *ssl = const_cast(reinterpret_cast(ch)); - if (SSL_client_hello_get0_ext(ssl, type, out, outlen) == 1) { - return TS_SUCCESS; - } #endif - return retval; -} - TSSslVerifyCTX TSVConnSslVerifyCTXGet(TSVConn sslp) { From 9b76bd0d34574e7362476f169f3376fb79561675 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Thu, 8 Jan 2026 11:45:44 +1100 Subject: [PATCH 08/11] Update plugin.cc --- plugins/experimental/ja4_fingerprint/plugin.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 3c5d0efaca1..b39dd8470e6 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -442,8 +442,6 @@ handle_vconn_close(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) int client_hello_ext_get(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen) { - TSReturnCode retval = TS_SUCCESS; - if (ch == nullptr) { return TS_ERROR; } @@ -460,5 +458,5 @@ client_hello_ext_get(TSClientHello ch, unsigned int type, const unsigned char ** } #endif - return retval; + return TS_ERROR; } From 51ebe553ef8c821517239dab282859ddc37aa5f4 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Thu, 8 Jan 2026 12:07:00 +1100 Subject: [PATCH 09/11] Update plugin.cc --- plugins/experimental/ja4_fingerprint/plugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index b39dd8470e6..2fe53d1d97b 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -243,7 +243,7 @@ get_version(TSClientHello ch) std::size_t buflen{}; if (TS_SUCCESS == client_hello_ext_get(ch, EXT_SUPPORTED_VERSIONS, &buf, &buflen)) { std::uint16_t max_version{0}; - uint8_t list_len = buf[0]; + size_t list_len = buf[0]; for (size_t i = 1; i + 1 < buflen && i < list_len + 1; i += 2) { std::uint16_t version = (buf[i] << 8) | buf[i + 1]; if (!JA4::is_GREASE(version) && version > max_version) { From b57f3384e1f61aa6f0fe8adce54d9a7173b0404d Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Thu, 8 Jan 2026 12:07:45 +1100 Subject: [PATCH 10/11] Update ts.h --- include/ts/ts.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/ts/ts.h b/include/ts/ts.h index c2b42435679..985f0693291 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -1334,7 +1334,6 @@ int TSVConnIsSsl(TSVConn sslp); int TSVConnProvidedSslCert(TSVConn sslp); const char *TSVConnSslSniGet(TSVConn sslp, int *length); TSClientHello TSVConnClientHelloGet(TSVConn sslp); -TSReturnCode TSVConnClientHelloExtGet(TSClientHello ch, unsigned int type, const unsigned char **out, size_t *outlen); TSSslSession TSSslSessionGet(const TSSslSessionID *session_id); int TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr); TSReturnCode TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session, TSSslConnection ssl_conn); From 162af784db8be4f4117a8f66a9948a51279a1d46 Mon Sep 17 00:00:00 2001 From: Jasmine Emanouel Date: Thu, 8 Jan 2026 13:58:53 +1100 Subject: [PATCH 11/11] Update apidefs.h.in --- include/ts/apidefs.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index aac11c2f68d..b6843e51153 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1080,7 +1080,7 @@ using TSHostLookupResult = struct tsapi_hostlookupresult *; using TSAIOCallback = struct tsapi_aiocallback *; using TSAcceptor = struct tsapi_net_accept *; using TSRemapPluginInfo = struct tsapi_remap_plugin_info *; -using TSClientHello = void *; +using TSClientHello = struct tsapi_ssl_client_hello *; using TSFetchSM = struct tsapi_fetchsm *;