This is an automated email from the ASF dual-hosted git repository.
bneradt 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 3b6c894780 SSL_CTX_get0_implemented_groups for TLS group metrics
(#12628)
3b6c894780 is described below
commit 3b6c894780d61d8a4ac6adde5ef865bef1f88e27
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Nov 4 15:27:53 2025 -0600
SSL_CTX_get0_implemented_groups for TLS group metrics (#12628)
Building on PR #11844 which added TLS group metrics, this change uses
SSL_CTX_get0_implemented_groups() to dynamically discover all supported
TLS groups at initialization, including KEMs (Key Encapsulation
Mechanisms) like X25519MLKEM768 and SecP256r1MLKEM768 that don't have
standard NIDs defined in older OpenSSL versions. This fixes issue #12622
where KEM groups were being reported as OTHER instead of their actual
group names. The implementation adds a new conditional compilation path
that uses string-based group maps (similar to BoringSSL) when
SSL_CTX_get0_implemented_groups is available, falling back to the
NID-based approach for older OpenSSL 3.x versions.
Fixes: #12622
---
CMakeLists.txt | 1 +
include/tscore/ink_config.h.cmake.in | 1 +
src/iocore/net/SSLStats.cc | 75 ++++++++++++++++++++++++++----------
src/iocore/net/SSLStats.h | 6 ++-
src/iocore/net/TLSBasicSupport.cc | 35 +++++++++++++++--
5 files changed, 92 insertions(+), 26 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 55181e373a..9e7d100c6b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -477,6 +477,7 @@ check_symbol_exists(SSL_get_shared_curve "openssl/ssl.h"
HAVE_SSL_GET_SHARED_CUR
check_symbol_exists(SSL_get_curve_name "openssl/ssl.h" HAVE_SSL_GET_CURVE_NAME)
check_symbol_exists(SSL_get0_group_name "openssl/ssl.h"
HAVE_SSL_GET0_GROUP_NAME)
check_symbol_exists(SSL_get_negotiated_group "openssl/ssl.h"
HAVE_SSL_GET_NEGOTIATED_GROUP)
+check_symbol_exists(SSL_CTX_get0_implemented_groups "openssl/ssl.h"
HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS)
check_symbol_exists(SSL_get_group_id "openssl/ssl.h" HAVE_SSL_GET_GROUP_ID)
check_symbol_exists(SSL_get_group_name "openssl/ssl.h" HAVE_SSL_GET_GROUP_NAME)
check_symbol_exists(SSL_group_to_name "openssl/ssl.h" HAVE_SSL_GROUP_TO_NAME)
diff --git a/include/tscore/ink_config.h.cmake.in
b/include/tscore/ink_config.h.cmake.in
index 02cc203422..24727649c7 100644
--- a/include/tscore/ink_config.h.cmake.in
+++ b/include/tscore/ink_config.h.cmake.in
@@ -177,6 +177,7 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@;
#cmakedefine01 HAVE_SSL_GET_CURVE_NAME
#cmakedefine01 HAVE_SSL_GET0_GROUP_NAME
#cmakedefine01 HAVE_SSL_GET_NEGOTIATED_GROUP
+#cmakedefine01 HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS
#cmakedefine01 HAVE_SSL_GET_GROUP_ID
#cmakedefine01 HAVE_SSL_GET_GROUP_NAME
#cmakedefine01 HAVE_SSL_GROUP_TO_NAME
diff --git a/src/iocore/net/SSLStats.cc b/src/iocore/net/SSLStats.cc
index 5d0b90c74c..fdec94b1e4 100644
--- a/src/iocore/net/SSLStats.cc
+++ b/src/iocore/net/SSLStats.cc
@@ -34,10 +34,10 @@
SSLStatsBlock ssl_rsb;
std::unordered_map<std::string, Metrics::Counter::AtomicType *> cipher_map;
-#ifdef OPENSSL_IS_BORINGSSL
+#if defined(OPENSSL_IS_BORINGSSL) || HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS
std::unordered_map<std::string, Metrics::Counter::AtomicType *> tls_group_map;
std::unordered_map<std::string, Metrics::Counter::AtomicType *>
tls_group_handshake_time_map;
-#elif defined(SSL_get_negotiated_group)
+#elif HAVE_SSL_GET_NEGOTIATED_GROUP
std::unordered_map<int, Metrics::Counter::AtomicType *> tls_group_map;
std::unordered_map<int, Metrics::Counter::AtomicType *>
tls_group_handshake_time_map;
#endif
@@ -50,7 +50,7 @@ DbgCtl dbg_ctl_ssl{"ssl"};
constexpr std::string_view UNKNOWN_CIPHER{"(NONE)"};
#endif
-#if defined(OPENSSL_IS_BORINGSSL) || defined(SSL_get_negotiated_group)
+#if defined(OPENSSL_IS_BORINGSSL) || HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS ||
HAVE_SSL_GET_NEGOTIATED_GROUP
template <typename T>
void
@@ -73,9 +73,10 @@ add_group_stat(T key, const std::string &name)
Dbg(dbg_ctl_ssl, "registering SSL group handshake time metric
'%s.handshake_time'", name.c_str());
}
}
-#endif // OPENSSL_IS_BORINGSSL or SSL_get_negotiated_group
+#endif // OPENSSL_IS_BORINGSSL or HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS or
HAVE_SSL_GET_NEGOTIATED_GROUP
-#if not defined(OPENSSL_IS_BORINGSSL) and defined(SSL_get_negotiated_group) //
OPENSSL 3.x
+#if not defined(OPENSSL_IS_BORINGSSL) and not
HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS and \
+ HAVE_SSL_GET_NEGOTIATED_GROUP // OPENSSL 3.x without
SSL_CTX_get0_implemented_groups
struct TLSGroup {
int nid;
@@ -115,7 +116,7 @@ const TLSGroup TLS_GROUPS[] = {
#endif
};
-#endif // OPENSSL 3.x
+#endif // OPENSSL 3.x without SSL_CTX_get0_implemented_groups
} // end anonymous namespace
@@ -294,16 +295,18 @@ SSLInitializeStatistics()
add_cipher_stat(cipher_name, stat_name);
}
#else
- // Get and register the SSL cipher stats. Note that we are using the default
SSL context to obtain
- // the cipher list. This means that the set of ciphers is fixed by the build
configuration and not
- // filtered by proxy.config.ssl.server.cipher_suite. This keeps the set of
cipher suites stable across
- // configuration reloads and works for the case where we honor the client
cipher preference.
- SSLMultiCertConfigLoader loader(nullptr);
- SSL_CTX *ctx = loader.default_server_ssl_ctx();
- SSL *ssl = SSL_new(ctx);
+ // Acquire the loaded SSL certificate configuration to enumerate ciphers and
groups.
+ // This must be called AFTER SSLCertificateConfig::startup().
+ SSLCertificateConfig::scoped_config lookup;
+ if (!lookup || !lookup->ssl_default) {
+ Dbg(dbg_ctl_ssl, "No SSL configuration, skipping cipher/group statistics
initialization");
+ return;
+ }
+
+ SSL_CTX *ctx = lookup->ssl_default.get();
+ SSL *ssl = SSL_new(ctx);
STACK_OF(SSL_CIPHER) *ciphers = SSL_get_ciphers(ssl);
- // BoringSSL has sk_SSL_CIPHER_num() return a size_t (well, sk_num() is)
for (int index = 0; index < static_cast<int>(sk_SSL_CIPHER_num(ciphers));
index++) {
SSL_CIPHER *cipher = const_cast<SSL_CIPHER
*>(sk_SSL_CIPHER_value(ciphers, index));
const char *cipherName = SSL_CIPHER_get_name(cipher);
@@ -312,14 +315,48 @@ SSLInitializeStatistics()
add_cipher_stat(cipherName, statName);
}
+ // TLS Group
+#if HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS
+ STACK_OF(OPENSSL_CSTRING) *group_names = sk_OPENSSL_CSTRING_new_null();
+ if (group_names == nullptr) {
+ Error("Failed to allocate stack for TLS group names");
+ } else {
+ constexpr int ALL_GROUPS = 1;
+ Dbg(dbg_ctl_ssl, "Calling SSL_CTX_get0_implemented_groups on loaded SSL
context");
+ if (SSL_CTX_get0_implemented_groups(ctx, ALL_GROUPS, group_names) != 1) {
+ Error("Failed to get implemented groups via
SSL_CTX_get0_implemented_groups");
+ }
+ int const num_groups = sk_OPENSSL_CSTRING_num(group_names);
+ Dbg(dbg_ctl_ssl, "SSL_CTX_get0_implemented_groups returned %d groups",
num_groups);
+
+ for (int index = 0; index < num_groups; index++) {
+ const char *name = sk_OPENSSL_CSTRING_value(group_names, index);
+ if (name == nullptr) {
+ Error("NULL group name returned for index %d in
SSL_CTX_get0_implemented_groups", index);
+ continue;
+ }
+ add_group_stat<std::string>(name, name);
+ }
+
+ // Note: We don't free group_names as the strings are owned by OpenSSL's
internal structures.
+ sk_OPENSSL_CSTRING_free(group_names);
+ }
+
+ // Add "OTHER" for groups not discovered.
+ add_group_stat<std::string>("OTHER", "OTHER");
+#elif HAVE_SSL_GET_NEGOTIATED_GROUP
+ // Use static NID table for group registration.
+ for (auto group : TLS_GROUPS) {
+ add_group_stat<int>(group.nid, group.name);
+ }
+#endif // HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS or HAVE_SSL_GET_NEGOTIATED_GROUP
+
SSL_free(ssl);
- SSLReleaseContext(ctx);
#endif
// Add "OTHER" for ciphers not on the map
add_cipher_stat(SSL_CIPHER_STAT_OTHER.c_str(),
"proxy.process.ssl.cipher.user_agent." + SSL_CIPHER_STAT_OTHER);
- // TLS Group
#if defined(OPENSSL_IS_BORINGSSL)
size_t list_size = SSL_get_all_group_names(nullptr, 0);
std::vector<const char *> group_list(list_size);
@@ -328,9 +365,5 @@ SSLInitializeStatistics()
for (const char *name : group_list) {
add_group_stat<std::string>(name, name);
}
-#elif defined(SSL_get_negotiated_group)
- for (auto group : TLS_GROUPS) {
- add_group_stat<int>(group.nid, group.name);
- }
-#endif // OPENSSL_IS_BORINGSSL or SSL_get_negotiated_group
+#endif // OPENSSL_IS_BORINGSSL
}
diff --git a/src/iocore/net/SSLStats.h b/src/iocore/net/SSLStats.h
index 7e326029f3..82b8444530 100644
--- a/src/iocore/net/SSLStats.h
+++ b/src/iocore/net/SSLStats.h
@@ -23,6 +23,7 @@
#pragma once
+#include "tscore/ink_config.h"
#include "tsutil/Metrics.h"
#include <openssl/ssl.h>
@@ -114,16 +115,17 @@ struct SSLStatsBlock {
extern SSLStatsBlock ssl_rsb;
extern std::unordered_map<std::string, Metrics::Counter::AtomicType *>
cipher_map;
-#if defined(OPENSSL_IS_BORINGSSL)
+#if defined(OPENSSL_IS_BORINGSSL) || HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS
extern std::unordered_map<std::string, Metrics::Counter::AtomicType *>
tls_group_map;
extern std::unordered_map<std::string, Metrics::Counter::AtomicType *>
tls_group_handshake_time_map;
-#elif defined(SSL_get_negotiated_group)
+#elif HAVE_SSL_GET_NEGOTIATED_GROUP
extern std::unordered_map<int, Metrics::Counter::AtomicType *> tls_group_map;
extern std::unordered_map<int, Metrics::Counter::AtomicType *>
tls_group_handshake_time_map;
constexpr int
SSL_GROUP_STAT_OTHER_KEY = 0;
#endif
// Initialize SSL statistics.
+// Must be called AFTER SSLCertificateConfig::startup() so SSL contexts are
loaded.
void SSLInitializeStatistics();
const std::string SSL_CIPHER_STAT_OTHER = "OTHER";
diff --git a/src/iocore/net/TLSBasicSupport.cc
b/src/iocore/net/TLSBasicSupport.cc
index c837126075..7510aeb428 100644
--- a/src/iocore/net/TLSBasicSupport.cc
+++ b/src/iocore/net/TLSBasicSupport.cc
@@ -22,6 +22,7 @@
limitations under the License.
*/
+#include "P_SSLUtils.h"
#include "SSLStats.h"
#include "iocore/net/TLSBasicSupport.h"
#if defined(OPENSSL_IS_BORINGSSL)
@@ -249,7 +250,21 @@ TLSBasicSupport::_record_tls_handshake_end_time()
Metrics::Counter::increment(it->second, ssl_handshake_time);
}
}
-#elif defined(SSL_get_negotiated_group)
+#elif HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS
+ SSL *ssl = this->_get_ssl_object();
+ std::string_view group_name = SSLGetGroupName(ssl);
+ if (!group_name.empty()) {
+ std::string group_str(group_name);
+ if (auto it = tls_group_handshake_time_map.find(group_str); it !=
tls_group_handshake_time_map.end()) {
+ Metrics::Counter::increment(it->second, ssl_handshake_time);
+ } else {
+ auto other = tls_group_handshake_time_map.find("OTHER");
+ if (other != tls_group_handshake_time_map.end()) {
+ Metrics::Counter::increment(other->second, ssl_handshake_time);
+ }
+ }
+ }
+#elif HAVE_SSL_GET_NEGOTIATED_GROUP
SSL *ssl = this->_get_ssl_object();
int nid = SSL_get_negotiated_group(const_cast<SSL *>(ssl));
if (nid != NID_undef) {
@@ -281,7 +296,21 @@ TLSBasicSupport::_update_end_of_handshake_stats()
Warning("Unknown TLS Group");
}
}
-#elif defined(SSL_get_negotiated_group)
+#elif HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS
+ SSL *ssl = this->_get_ssl_object();
+ std::string_view group_name = SSLGetGroupName(ssl);
+ if (!group_name.empty()) {
+ std::string group_str(group_name);
+ if (auto it = tls_group_map.find(group_str); it != tls_group_map.end()) {
+ Metrics::Counter::increment(it->second);
+ } else {
+ auto other = tls_group_map.find("OTHER");
+ if (other != tls_group_map.end()) {
+ Metrics::Counter::increment(other->second);
+ }
+ }
+ }
+#elif HAVE_SSL_GET_NEGOTIATED_GROUP
SSL *ssl = this->_get_ssl_object();
int nid = SSL_get_negotiated_group(const_cast<SSL *>(ssl));
if (nid != NID_undef) {
@@ -292,5 +321,5 @@ TLSBasicSupport::_update_end_of_handshake_stats()
Metrics::Counter::increment(other->second);
}
}
-#endif // OPENSSL_IS_BORINGSSL or SSL_get_negotiated_group
+#endif // OPENSSL_IS_BORINGSSL or HAVE_SSL_CTX_GET0_IMPLEMENTED_GROUPS or
HAVE_SSL_GET_NEGOTIATED_GROUP
}