From 65984bd9a56559d4cc3c2877f0af777f624a8cdd Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Wed, 13 May 2026 19:51:22 +0200 Subject: [PATCH v6] 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(); type | name ------------+-------------------- negotiated | X25519MLKEM768 shared | X25519MLKEM768 shared | x25519 supported | X25519MLKEM768 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 --- configure | 36 +++++ configure.ac | 4 + contrib/sslinfo/Makefile | 2 +- contrib/sslinfo/meson.build | 1 + contrib/sslinfo/sslinfo--1.2--1.3.sql | 10 ++ contrib/sslinfo/sslinfo.c | 192 +++++++++++++++++++++++++- contrib/sslinfo/sslinfo.control | 2 +- doc/src/sgml/sslinfo.sgml | 45 ++++++ meson.build | 5 + src/include/pg_config.h.in | 11 ++ src/tools/pgindent/typedefs.list | 1 + 11 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql diff --git a/configure b/configure index f66c1054a7a..2a708df5afd 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 8d176bd3468..2641a17d317 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..40fd0ea2b9c --- /dev/null +++ b/contrib/sslinfo/sslinfo--1.2--1.3.sql @@ -0,0 +1,10 @@ +/* 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(OUT group_type text, OUT name text +) RETURNS SETOF record +AS 'MODULE_PATHNAME', 'ssl_group_info' +LANGUAGE C STRICT PARALLEL RESTRICTED; diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c index 2b9eb90b093..8807f50c908 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(X509_NAME *name, text *fieldName); static Datum ASN1_STRING_to_text(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,170 @@ 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 */ + 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("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(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unknown OpenSSL group at position %d", + call_cntr))); + + 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..34fc187e4e4 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. + + + + + + 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 20b887f1a1b..90e99705656 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 @@ -2901,6 +2904,8 @@ decl_checks = [ ['strlcpy', 'string.h'], ['strsep', 'string.h'], ['timingsafe_bcmp', 'string.h'], + ['SSL_get1_groups', 'openssl/ssl.h'], + ['SSL_get_negotiated_group', 'openssl/ssl.h'], ] # Need to check for function declarations for these functions, because diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 4f8113c144b..67090821722 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 cbd9e10fc1d..cb8b4ea306e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2777,6 +2777,7 @@ SQLValueFunction SQLValueFunctionOp SSL SSLExtensionInfoContext +SSLGroupInfoContext SSL_CTX STARTUPINFO STRLEN base-commit: 3bf63730cb041a834c618082ba3d5e8bf96a31a5 -- 2.52.0