From d063331d29058ae8a3795a37364dcfa12d87e305 Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Wed, 13 May 2026 19:51:22 +0200 Subject: [PATCH v7] contrib/sslinfo: Add ssl_group_info Add a new function to sslinfo ssl_group_info to show SSL groups, including negotiated, supported and shared. It's useful for diagnostic purposes, to identify what's being used and supported, e.g. which key share is being negotiated. Few examples, for openssl 3.2.4: select * from ssl_group_info(); group_type | name -------------------------+-------------------- negotiated | X25519MLKEM768 shared | X25519MLKEM768 shared | x25519 client supported | X25519MLKEM768 client supported | x25519 [...] The implementation is inspired by ssl_print_groups from openssl. Reviewed-by: Daniel Gustafsson Reviewed-by: Jacob Champion Reviewed-by: Cary Huang Reviewed-by: Zsolt Parragi Reviewed-by: Evan Si --- configure | 36 +++++ configure.ac | 4 + contrib/sslinfo/Makefile | 2 +- contrib/sslinfo/meson.build | 1 + contrib/sslinfo/sslinfo--1.2--1.3.sql | 16 +++ contrib/sslinfo/sslinfo.c | 200 +++++++++++++++++++++++++- contrib/sslinfo/sslinfo.control | 2 +- doc/src/sgml/sslinfo.sgml | 45 ++++++ meson.build | 11 ++ src/include/pg_config.h.in | 11 ++ src/tools/pgindent/typedefs.list | 1 + 11 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql diff --git a/configure b/configure index d42a7a794ff..53cacb5363f 100755 --- a/configure +++ b/configure @@ -13189,6 +13189,42 @@ _ACEOF fi done + # Functions for groups support, some of them are real functions, some are + # just wrappers around SSL_ctrl. + for ac_func in SSL_group_to_name +do : + ac_fn_c_check_func "$LINENO" "SSL_group_to_name" "ac_cv_func_SSL_group_to_name" +if test "x$ac_cv_func_SSL_group_to_name" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SSL_GROUP_TO_NAME 1 +_ACEOF + +fi +done + +ac_fn_c_check_decl "$LINENO" "SSL_get1_groups" "ac_cv_have_decl_SSL_get1_groups" "#include +" +if test "x$ac_cv_have_decl_SSL_get1_groups" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_SSL_GET1_GROUPS $ac_have_decl +_ACEOF +ac_fn_c_check_decl "$LINENO" "SSL_get_negotiated_group" "ac_cv_have_decl_SSL_get_negotiated_group" "#include +" +if test "x$ac_cv_have_decl_SSL_get_negotiated_group" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_SSL_GET_NEGOTIATED_GROUP $ac_have_decl +_ACEOF + $as_echo "#define USE_OPENSSL 1" >>confdefs.h diff --git a/configure.ac b/configure.ac index a331749fcb5..930cfc433dd 100644 --- a/configure.ac +++ b/configure.ac @@ -1444,6 +1444,10 @@ if test "$with_ssl" = openssl ; then AC_CHECK_FUNCS([SSL_CTX_set_cert_cb]) # Function introduced in OpenSSL 1.1.1, not in LibreSSL. AC_CHECK_FUNCS([X509_get_signature_info SSL_CTX_set_num_tickets SSL_CTX_set_keylog_callback SSL_CTX_set_client_hello_cb]) + # Functions for groups support, some of them are real functions, some are + # just wrappers around SSL_ctrl. + AC_CHECK_FUNCS([SSL_group_to_name]) + AC_CHECK_DECLS([SSL_get1_groups, SSL_get_negotiated_group], [], [], [#include ]) AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)]) elif test "$with_ssl" != no ; then AC_MSG_ERROR([--with-ssl must specify openssl]) diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile index 14305594e2d..d968ef2abfd 100644 --- a/contrib/sslinfo/Makefile +++ b/contrib/sslinfo/Makefile @@ -6,7 +6,7 @@ OBJS = \ sslinfo.o EXTENSION = sslinfo -DATA = sslinfo--1.2.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql +DATA = sslinfo--1.2.sql sslinfo--1.2--1.3.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql PGFILEDESC = "sslinfo - information about client SSL certificate" ifdef USE_PGXS diff --git a/contrib/sslinfo/meson.build b/contrib/sslinfo/meson.build index 6e9cb96430a..27737562925 100644 --- a/contrib/sslinfo/meson.build +++ b/contrib/sslinfo/meson.build @@ -26,6 +26,7 @@ install_data( 'sslinfo--1.0--1.1.sql', 'sslinfo--1.1--1.2.sql', 'sslinfo--1.2.sql', + 'sslinfo--1.2--1.3.sql', 'sslinfo.control', kwargs: contrib_data_args, ) diff --git a/contrib/sslinfo/sslinfo--1.2--1.3.sql b/contrib/sslinfo/sslinfo--1.2--1.3.sql new file mode 100644 index 00000000000..25588f95efc --- /dev/null +++ b/contrib/sslinfo/sslinfo--1.2--1.3.sql @@ -0,0 +1,16 @@ +/* contrib/sslinfo/sslinfo--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION sslinfo UPDATE TO '1.3'" to load this file. \quit + +CREATE FUNCTION +ssl_group_info_with_nulls(OUT group_type text, OUT name text +) RETURNS SETOF record +AS 'MODULE_PATHNAME', 'ssl_group_info' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION +ssl_group_info(OUT group_type text, OUT name text +) RETURNS SETOF record AS $$ +SELECT * FROM ssl_group_info_with_nulls() WHERE name IS NOT NULL; +$$ LANGUAGE SQL; diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c index c4ae847880d..2802a97764c 100644 --- a/contrib/sslinfo/sslinfo.c +++ b/contrib/sslinfo/sslinfo.c @@ -19,6 +19,14 @@ #include "miscadmin.h" #include "utils/builtins.h" +#if HAVE_DECL_SSL_GET1_GROUPS && \ + HAVE_DECL_SSL_GET_NEGOTIATED_GROUP && \ + defined(HAVE_SSL_GROUP_TO_NAME) \ + +#define HAVE_SSL_GROUPS + +#endif + PG_MODULE_MAGIC_EXT( .name = "sslinfo", .version = PG_VERSION @@ -28,13 +36,28 @@ static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName); static Datum ASN1_STRING_to_text(const ASN1_STRING *str); /* - * Function context for data persisting over repeated calls. + * Function context for data persisting over repeated calls of + * ssl_extension_info. */ typedef struct { TupleDesc tupdesc; } SSLExtensionInfoContext; +/* + * Function context for data persisting over repeated calls of + * ssl_group_info. + */ +typedef struct +{ + TupleDesc tupdesc; + int nshared; + int nsupported; + + /* Supported groups have to be stored separately */ + int *supported_groups; +} SSLGroupInfoContext; + /* * Indicates whether current session uses SSL * @@ -474,3 +497,178 @@ ssl_extension_info(PG_FUNCTION_ARGS) /* All done */ SRF_RETURN_DONE(funcctx); } + +/* + * Returns information about TLS groups. + * + * Returns setof record made of the following values: + * - type of the group: negotiated, shared, supported. + * - name of the group. + */ +PG_FUNCTION_INFO_V1(ssl_group_info); +Datum +ssl_group_info(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_SSL_GROUPS + /* + * If we lack SSL groups API, we still need to do SRF stuff. Thus the + * condition doesn't cover the whole function, but only parts of it. This + * particular one is only to avoid unused variable warning, in case if + * HAVE_SSL_GROUPS is false. + */ + SSL *ssl = MyProcPort->ssl; +#endif + + FuncCallContext *funcctx; + int call_cntr = 0; + int max_calls = 0; + MemoryContext oldcontext; + SSLGroupInfoContext *fctx; + + if (SRF_IS_FIRSTCALL()) + { + + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * Switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Create a user function context for cross-call persistence */ + fctx = palloc_object(SSLGroupInfoContext); + + /* Construct tuple descriptor */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context that cannot accept type record"))); + fctx->tupdesc = BlessTupleDesc(tupdesc); + + if (!MyProcPort->ssl_in_use) + { + /* fast track when no results */ + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + +#ifdef HAVE_SSL_GROUPS + if (ssl != NULL) + { + fctx->nsupported = SSL_get1_groups(ssl, NULL); + fctx->nshared = SSL_get_shared_group(ssl, -1); + + fctx->supported_groups = + palloc(fctx->nsupported * sizeof(*fctx->supported_groups)); + SSL_get1_groups(ssl, fctx->supported_groups); + + /* + * Set max_calls as the number of supported groups plus the number + * of shared groups plus one negotiated group. + */ + max_calls = fctx->nsupported + fctx->nshared + 1; + } + + if (max_calls > 0) + { + /* got results, keep track of them */ + funcctx->max_calls = max_calls; + funcctx->user_fctx = fctx; + } + else + { + /* fast track when no results */ + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } +#else + /* SSL groups API is not present, skip */ + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SSL groups API is not supported"))); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); +#endif + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * Initialize per-call variables. + */ + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + fctx = funcctx->user_fctx; + + /* do while there are more left to send */ + if (call_cntr < max_calls) + { +#ifdef HAVE_SSL_GROUPS + Datum values[2]; + bool nulls[2]; + HeapTuple tuple; + Datum result, + group_type; + int nid; + const char *group_name; + + /* Send the negotiated group first */ + if (call_cntr == 0) + { + nid = SSL_get_negotiated_group(ssl); + group_type = CStringGetTextDatum("negotiated"); + } + /* Then the shared groups */ + else if (call_cntr < fctx->nshared + 1) + { + nid = SSL_get_shared_group(ssl, call_cntr - 1); + group_type = CStringGetTextDatum("shared"); + } + /* And finally the supported groups */ + else if (call_cntr < fctx->nsupported + fctx->nshared + 1) + { + nid = fctx->supported_groups[call_cntr - fctx->nshared - 1]; + group_type = CStringGetTextDatum("client supported"); + } + else + SRF_RETURN_DONE(funcctx); + + /* + * SSL_group_to_name can return NULL in case of an error, e.g. when no + * such name was registered for some reason. + */ + group_name = SSL_group_to_name(ssl, nid); + if (group_name == NULL) + { + ereport(WARNING, + (errcode(ERRCODE_WARNING), + errmsg("unknown OpenSSL group at position %d", + call_cntr))); + + SRF_RETURN_NEXT_NULL(funcctx); + } + + values[0] = group_type; + nulls[0] = false; + + values[1] = CStringGetTextDatum(group_name); + nulls[1] = false; + + /* Build tuple */ + tuple = heap_form_tuple(fctx->tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); +#endif + } + + /* All done */ + SRF_RETURN_DONE(funcctx); +} diff --git a/contrib/sslinfo/sslinfo.control b/contrib/sslinfo/sslinfo.control index c7754f924cf..b53e95b7da8 100644 --- a/contrib/sslinfo/sslinfo.control +++ b/contrib/sslinfo/sslinfo.control @@ -1,5 +1,5 @@ # sslinfo extension comment = 'information about SSL certificates' -default_version = '1.2' +default_version = '1.3' module_pathname = '$libdir/sslinfo' relocatable = true diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml index 85d49f66537..a7841477713 100644 --- a/doc/src/sgml/sslinfo.sgml +++ b/doc/src/sgml/sslinfo.sgml @@ -240,6 +240,51 @@ emailAddress + + + + ssl_group_info() returns setof record + + ssl_group_info + + + + + Provide information about TLS groups: group type and group name. + The group type value could be one of the following: + + + + negotiated + + + The group used for the handshake key exchange process. + + + + + + shared + + + List of named groups shared with the server side. + + + + + + client supported + + + list of named groups supported by the client for key exchange in the + form of "supported_groups" extension. + + + + + + + diff --git a/meson.build b/meson.build index f4cde249242..1c5cda8a4d8 100644 --- a/meson.build +++ b/meson.build @@ -1677,6 +1677,9 @@ if sslopt in ['auto', 'openssl'] ['SSL_CTX_set_num_tickets'], ['SSL_CTX_set_keylog_callback'], ['SSL_CTX_set_client_hello_cb'], + + # Function for groups support. + ['SSL_group_to_name'], ] are_openssl_funcs_complete = true @@ -2904,6 +2907,14 @@ decl_checks += [ ['memset_s', 'string.h', '#define __STDC_WANT_LIB_EXT1__ 1'], ] +if ssl.found() + decl_checks += [ + # Declarations for groups support. + ['SSL_get1_groups', 'openssl/ssl.h'], + ['SSL_get_negotiated_group', 'openssl/ssl.h'], + ] +endif + foreach c : decl_checks func = c.get(0) header = c.get(1) diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 661c4a9b168..60ebeb6cfee 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -101,6 +101,14 @@ don't. */ #undef HAVE_DECL_PWRITEV +/* Define to 1 if you have the declaration of `SSL_get1_groups', and to 0 if + you don't. */ +#undef HAVE_DECL_SSL_GET1_GROUPS + +/* Define to 1 if you have the declaration of `SSL_get_negotiated_group', and + to 0 if you don't. */ +#undef HAVE_DECL_SSL_GET_NEGOTIATED_GROUP + /* Define to 1 if you have the declaration of `strchrnul', and to 0 if you don't. */ #undef HAVE_DECL_STRCHRNUL @@ -384,6 +392,9 @@ /* Define to 1 if you have the `SSL_CTX_set_num_tickets' function. */ #undef HAVE_SSL_CTX_SET_NUM_TICKETS +/* Define to 1 if you have the `SSL_group_to_name' function. */ +#undef HAVE_SSL_GROUP_TO_NAME + /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c546b3d6375..0a3b9c159c7 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2787,6 +2787,7 @@ SQLValueFunction SQLValueFunctionOp SSL SSLExtensionInfoContext +SSLGroupInfoContext SSL_CTX STARTUPINFO STRLEN base-commit: 534db08f972852b77ea5e96e77d5fe45f3d8df93 -- 2.55.0