Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vw31U-00HM9q-0T for pgsql-hackers@arkaria.postgresql.org; Fri, 27 Feb 2026 18:57:12 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vw31S-005nHy-31 for pgsql-hackers@arkaria.postgresql.org; Fri, 27 Feb 2026 18:57:10 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from <9erthalion6@gmail.com>) id 1vw31S-005nHe-1d for pgsql-hackers@lists.postgresql.org; Fri, 27 Feb 2026 18:57:10 +0000 Received: from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from <9erthalion6@gmail.com>) id 1vw31P-00000001XxS-0c1m for pgsql-hackers@postgresql.org; Fri, 27 Feb 2026 18:57:09 +0000 Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-4837f27cf2dso20760785e9.2 for ; Fri, 27 Feb 2026 10:57:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772218627; x=1772823427; darn=postgresql.org; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:from:to :cc:subject:date:message-id:reply-to; bh=4rAxpEQxbPPuhTFUcQtVhUGN4N5FZyzccGaxYMCHTDQ=; b=cMsmn9VrMUzdZ1a5daFShK1OVTGAxKOBfnordrUG1BDCZKNUr6xECoEfEu196TNSZA vSlWChqrU0alPcBo7oC0TcHMrr/TQDWsdSVsVL3E/NQLPIvk/6D415sximV1sR3zlbZ4 nO+BoHrU16zJ3I8q/M+l5DW8jZmP2jPghYgcxwY9UriWdI5Gn8ViilOpj+MIJcju3tP4 XoGBfhxOJCCsZ9c7LFrN5LOZDMfM+M/M3WPNZ36RHRE45fWcEn0QpYFIJo2hL8VFfvsH kEywJ3GoPrxteZo+6bV61vSLdep/pFJlm7aCOmdI0QWPOodbOURjdTHylCvjuTSiXA9F jGow== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772218627; x=1772823427; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=4rAxpEQxbPPuhTFUcQtVhUGN4N5FZyzccGaxYMCHTDQ=; b=jpeugE2vyiPqj91Ra7A2dCLuQpY3nnGsRVbkcbEGIoBcQysuVRaI0Ch9vMGZw+hGsY +HLSFxQIASViZTga337reBLhrBnkkWSz1W2tSazAt0XYfGnzu1JDbEZhE1YTjgnZH0qQ 6m/yL9RcddxejLbfPDNM0IBJNBqvWw8Tcjsftj6OPwXWVrQZc8KrQTuj3zceX76KgKE9 W2AoO2jB/jg8s6W76/a5Rr89IRWVBqQjgAX+ZDDjtbcGxJO1XYIjcIMgcFqtFgcQD+Bx d57+vIgSJtRiZ7Me1zXjm8PfYMFyY7qFMGNBawYgkKEECe24fz+qNs3m3e/WC8V4M5Ry eo2w== X-Forwarded-Encrypted: i=1; AJvYcCUUUgXTA2/VnWAt0UAh5IaDEbN+ekGq2MBz5vKK2CjBNRPqQXnViYbCCPsEBAFN03sqC68V7/Eh3VqdR8fq@postgresql.org X-Gm-Message-State: AOJu0Yyl15nd589SHOAz8iAvNhzUjVECf0AD5xC7Oil86tstA/xysuA+ AdCzlUay4OrdvUFWO2qLvP71D+rYHBwqZR/yzgTXLhM6Vp0p6oTujFZj X-Gm-Gg: ATEYQzz5cPXk1H8sTnC76+u2pAYCCRz96ER8bAu0sRqbITVnTeivNNmgVNGyH8wSVA2 e1Ka2++vt8xzgc1tcJ1tjhZR/0WcVFJOBhQIGhEiMKzNlBtuhXfKiG3Pi5JKaokokBwaBHxpKHP s0jLORDKZBTPZyo3//n2/6U0TWP1a1Bs7TGjPFSrSEEL8Fv36pUFc+AQjr++u+def+BSyRT67If Wh10xMhXVlHtp095HA5bmVpQJCQ7lvMgXyKUs1se8uIb8weqkP6DWu+/k/EZUG+nqwQYPI6nxXd 6pdM25GTDJOWqqLXjH2higEjp8PS4lLAJjyWZSRTWOoV4piaWmc9gNb+u9aeKxJU8bFF+nPngr+ hVaQw4xlQJvx2D0N9fGtZzwuDxZ+D6b8vIQznrbhCnAPN/yuP6B3lHasc0a7mKjT9dmLks65eDF kQ5j1ddd6Ih/Va0+Ji1oDqpfX2njEP6lHzn93uB1jeYGgfeZ25aRH/K3AIz5bmW7HGMLCP49ebV U3AZGlboMYf9ar4HHUMCWQHrwPCRSPChklV X-Received: by 2002:a05:600c:8106:b0:483:456a:5146 with SMTP id 5b1f17b1804b1-483c9c24049mr56294885e9.25.1772218626514; Fri, 27 Feb 2026 10:57:06 -0800 (PST) Received: from ddolgov-thinkpadt14sgen1.rmtde.csb (dslb-002-207-075-089.002.207.pools.vodafone-ip.de. [2.207.75.89]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-483bfb9d9c6sm64797485e9.14.2026.02.27.10.57.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Feb 2026 10:57:04 -0800 (PST) Date: Fri, 27 Feb 2026 19:57:00 +0100 From: Dmitry Dolgov <9erthalion6@gmail.com> To: Jacob Champion Cc: Daniel Gustafsson , PostgreSQL Hackers Subject: Re: Add ssl_(supported|shared)_groups to sslinfo Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="bjvr4kzjz3vjgd5w" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --bjvr4kzjz3vjgd5w Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit > On Mon, Feb 23, 2026 at 11:22:22AM -0800, Jacob Champion wrote: > On Mon, Feb 23, 2026 at 9:58 AM Dmitry Dolgov <9erthalion6@gmail.com> wrote: > > No deep reason, it was just useful for some particular experiments and > > for gathering understanding of what's going on. Would you find it > > reasonable to have both, shared groups and the negotiated group, or > > having only the latter is strictly better? > > Well, take this with a grain of salt, because I tend to use tools > other than sslinfo for TLS debugging. But it seems to me that all of > the sslinfo functions cater to facts about the current connection: the > client certificate, the cipher, the protocol version. > > These new functions instead focus on what *might* have been, which > makes them kind of awkward. Maybe sslinfo should be expanded to give > us those tools as well, but I wonder if handshake debugging might be a > better fit for some debug logging on the server side. Or if there > might be an overall feature here -- "why did the negotiation behave > this way?" -- that could be better served by something that's not a > new array of sslinfo functions that have to be correlated with each > other. I see what you mean, an interesting point. After some pondering and looking at the history of sslinfo it looks like its purpose was already extended once beyond what was originally intended. AFAICT the initial implementation was concerning itself only with the information about SSL certificates (surprisingly even now the extension comment and documentation say "information about SSL certificates"), and now it also features the current cipher and version. I take it as an argument that expanding sslinfo goal and focus is not a problem, as long as it's clearly communicated and documented. What do you think? > (Also, while I was taking a look at ssl_extension_info(), I realized > that it's focused on certificate extensions and not protocol > extensions. It's kind of unfortunately named.) Yeah, that's unfortunate. I've ended up introducing a similarly looking ssl_group_info, which returns a set of record representing groups. --bjvr4kzjz3vjgd5w Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="v2-0001-contrib-sslinfo-Add-ssl_group_info.patch" From 69267b00cfe04601dc7be2fd178c55e5b5616e32 Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Thu, 19 Feb 2026 16:33:17 +0100 Subject: [PATCH v2] 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. --- contrib/sslinfo/Makefile | 2 +- contrib/sslinfo/meson.build | 2 +- contrib/sslinfo/sslinfo--1.2--1.3.sql | 10 ++ contrib/sslinfo/sslinfo.c | 167 +++++++++++++++++++++++++- contrib/sslinfo/sslinfo.control | 2 +- doc/src/sgml/sslinfo.sgml | 17 +++ src/tools/pgindent/typedefs.list | 1 + 7 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile index 14305594e2d..dc837209c93 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--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..29c7da44228 100644 --- a/contrib/sslinfo/meson.build +++ b/contrib/sslinfo/meson.build @@ -25,7 +25,7 @@ contrib_targets += sslinfo 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..e018010d4be 100644 --- a/contrib/sslinfo/sslinfo.c +++ b/contrib/sslinfo/sslinfo.c @@ -28,13 +28,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 +489,153 @@ 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) +{ + SSL *ssl = MyProcPort->ssl; + 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); + } + + 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); + } + + 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) + { + 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); + } + + /* 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..8ba2302fe08 100644 --- a/doc/src/sgml/sslinfo.sgml +++ b/doc/src/sgml/sslinfo.sgml @@ -240,6 +240,23 @@ 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 + or supported. The latter two are used mostly for diagnostic purposes. + + + diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 241945734ec..74eb3043dbb 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2694,6 +2694,7 @@ SQLValueFunction SQLValueFunctionOp SSL SSLExtensionInfoContext +SSLGroupInfoContext SSL_CTX STARTUPINFO STRLEN base-commit: 5b93a5987bd704d2363295eee919eee45f84c286 -- 2.52.0 --bjvr4kzjz3vjgd5w--