agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedAdd ssl_(supported|shared)_groups to sslinfo
22+ messages / 6 participants
[nested] [flat]
* Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-19 17:44 Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 1 reply; 22+ messages in thread
From: Dmitry Dolgov @ 2026-02-19 17:44 UTC (permalink / raw)
To: pgsql-hackers
Hi,
I've been experimenting with ssl recently, and found it's useful to have
more information exposed via contrib/sslinfo, in particular
ssl_supported_groups and ssl_shared_groups to show TLS groups extension.
I think it makes sense to add this, hence the patch. Any thoughts?
From 40b40a4f6456832a595e4371698e2003ede5fcbf Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v1] contrib/sslinfo: Add ssl_(supported|shared)_groups
Add new functions to sslinfo to show TLS groups extension, both
supported and shared. It's useful for identifying what's being used and
supported, e.g. which key share is being negotiated. Few examples, for
openssl 3.2.4:
=# select ssl_shared_groups();
ssl_shared_groups
-------------------
x25519:[...]
=# select ssl_supported_groups();
ssl_supported_groups
--------------------------------------
x25519:[...]
And the same for openssl 3.5 with different defaults:
=# select ssl_shared_groups();
ssl_shared_groups
---------------------------------
X25519MLKEM768:[...]
=# select ssl_supported_groups();
ssl_supported_groups
-------------------------------------
X25519MLKEM768:[...]
Do not add those functions into the pg_ssl_stats, because they could become
quite large, bloating PgBackendSSLStatus.
The implementation is inspired by ssl_print_groups from openssl.
---
contrib/sslinfo/Makefile | 2 +-
contrib/sslinfo/meson.build | 3 +-
contrib/sslinfo/sslinfo--1.2--1.3.sql | 12 ++++++
contrib/sslinfo/sslinfo--1.3.sql | 56 ++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 31 ++++++++++++++
contrib/sslinfo/sslinfo.control | 2 +-
src/backend/libpq/be-secure-openssl.c | 58 +++++++++++++++++++++++++++
src/include/libpq/libpq-be.h | 2 +
8 files changed, 163 insertions(+), 3 deletions(-)
create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql
create mode 100644 contrib/sslinfo/sslinfo--1.3.sql
diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile
index 14305594e2d..d278f02c093 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.3.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..0258813dde7 100644
--- a/contrib/sslinfo/meson.build
+++ b/contrib/sslinfo/meson.build
@@ -25,7 +25,8 @@ 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--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..d2cea79faa7
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.2--1.3.sql
@@ -0,0 +1,12 @@
+/* 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_supported_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_supported_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_shared_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_shared_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/sslinfo/sslinfo--1.3.sql b/contrib/sslinfo/sslinfo--1.3.sql
new file mode 100644
index 00000000000..98b51900959
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.3.sql
@@ -0,0 +1,56 @@
+/* contrib/sslinfo/sslinfo--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION sslinfo" to load this file. \quit
+
+CREATE FUNCTION ssl_client_serial() RETURNS numeric
+AS 'MODULE_PATHNAME', 'ssl_client_serial'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_is_used() RETURNS boolean
+AS 'MODULE_PATHNAME', 'ssl_is_used'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_version() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_version'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_cipher() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_cipher'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_cert_present() RETURNS boolean
+AS 'MODULE_PATHNAME', 'ssl_client_cert_present'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_dn_field(text) RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_client_dn_field'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_issuer_field(text) RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_issuer_field'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_dn() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_client_dn'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_issuer_dn() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_issuer_dn'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_supported_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_supported_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_shared_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_shared_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION
+ssl_extension_info(OUT name text,
+ OUT value text,
+ OUT critical boolean
+) RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'ssl_extension_info'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 2b9eb90b093..3efdca6d6bc 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -88,6 +88,37 @@ ssl_cipher(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
+PG_FUNCTION_INFO_V1(ssl_supported_groups);
+Datum
+ssl_supported_groups(PG_FUNCTION_ARGS)
+{
+ const char *groups;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ groups = be_tls_get_supported_groups(MyProcPort);
+ if (groups == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(groups));
+}
+
+PG_FUNCTION_INFO_V1(ssl_shared_groups);
+Datum
+ssl_shared_groups(PG_FUNCTION_ARGS)
+{
+ const char *groups;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ groups = be_tls_get_shared_groups(MyProcPort);
+ if (groups == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(groups));
+}
/*
* Indicates whether current client provided a certificate
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/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 4da6ac22ff9..b134fc53f5f 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -51,6 +51,8 @@
#endif
#include <openssl/x509v3.h>
+#define SSL_SUPPORTED_GROUPS 0
+#define SSL_SHARED_GROUPS 1
/* default init hook can be overridden by a shared library */
static void default_openssl_tls_init(SSL_CTX *context, bool isServerStart);
@@ -1560,6 +1562,62 @@ be_tls_get_cipher(Port *port)
return NULL;
}
+static const char *
+be_tls_get_groups_internal(Port *port, int type)
+{
+ if (port->ssl)
+ {
+ int i, ngroups, *groups, nid;
+ StringInfoData str;
+
+ if (type == SSL_SUPPORTED_GROUPS)
+ ngroups = SSL_get1_groups(port->ssl, NULL);
+ else
+ ngroups = SSL_get_shared_group(port->ssl, -1);
+
+ if (ngroups <= 0)
+ return NULL;
+
+ groups = palloc(ngroups * sizeof(*groups));
+ initStringInfo(&str);
+
+ if (type == SSL_SUPPORTED_GROUPS)
+ SSL_get1_groups(port->ssl, groups);
+
+ for (i = 0; i < ngroups; i++)
+ {
+ const char *name;
+
+ if (i)
+ appendStringInfo(&str, ":");
+
+ if (type == SSL_SUPPORTED_GROUPS)
+ nid = groups[i];
+ else
+ nid = SSL_get_shared_group(port->ssl, i);
+
+ name = SSL_group_to_name(port->ssl, nid);
+ appendStringInfoString(&str, ((name != NULL) ? name : "(null)"));
+ }
+
+ return str.data;
+ }
+ else
+ return NULL;
+}
+
+const char *
+be_tls_get_supported_groups(Port *port)
+{
+ return be_tls_get_groups_internal(port, SSL_SUPPORTED_GROUPS);
+}
+
+const char *
+be_tls_get_shared_groups(Port *port)
+{
+ return be_tls_get_groups_internal(port, SSL_SHARED_GROUPS);
+}
+
void
be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
{
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 921b2daa4ff..23e11fcb89d 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -317,6 +317,8 @@ extern ssize_t be_tls_write(Port *port, const void *ptr, size_t len, int *waitfo
extern int be_tls_get_cipher_bits(Port *port);
extern const char *be_tls_get_version(Port *port);
extern const char *be_tls_get_cipher(Port *port);
+extern const char *be_tls_get_supported_groups(Port *port);
+extern const char *be_tls_get_shared_groups(Port *port);
extern void be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len);
extern void be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len);
extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
base-commit: 5b93a5987bd704d2363295eee919eee45f84c286
--
2.52.0
Attachments:
[text/plain] v1-0001-contrib-sslinfo-Add-ssl_-supported-shared-_groups.patch (9.2K, ../../d57duqvzkxe43oons3jkdq7pj2wacidg7qorxommri74evu3l2@4x53she7mf77/2-v1-0001-contrib-sslinfo-Add-ssl_-supported-shared-_groups.patch)
download | inline diff:
From 40b40a4f6456832a595e4371698e2003ede5fcbf Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v1] contrib/sslinfo: Add ssl_(supported|shared)_groups
Add new functions to sslinfo to show TLS groups extension, both
supported and shared. It's useful for identifying what's being used and
supported, e.g. which key share is being negotiated. Few examples, for
openssl 3.2.4:
=# select ssl_shared_groups();
ssl_shared_groups
-------------------
x25519:[...]
=# select ssl_supported_groups();
ssl_supported_groups
--------------------------------------
x25519:[...]
And the same for openssl 3.5 with different defaults:
=# select ssl_shared_groups();
ssl_shared_groups
---------------------------------
X25519MLKEM768:[...]
=# select ssl_supported_groups();
ssl_supported_groups
-------------------------------------
X25519MLKEM768:[...]
Do not add those functions into the pg_ssl_stats, because they could become
quite large, bloating PgBackendSSLStatus.
The implementation is inspired by ssl_print_groups from openssl.
---
contrib/sslinfo/Makefile | 2 +-
contrib/sslinfo/meson.build | 3 +-
contrib/sslinfo/sslinfo--1.2--1.3.sql | 12 ++++++
contrib/sslinfo/sslinfo--1.3.sql | 56 ++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 31 ++++++++++++++
contrib/sslinfo/sslinfo.control | 2 +-
src/backend/libpq/be-secure-openssl.c | 58 +++++++++++++++++++++++++++
src/include/libpq/libpq-be.h | 2 +
8 files changed, 163 insertions(+), 3 deletions(-)
create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql
create mode 100644 contrib/sslinfo/sslinfo--1.3.sql
diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile
index 14305594e2d..d278f02c093 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.3.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..0258813dde7 100644
--- a/contrib/sslinfo/meson.build
+++ b/contrib/sslinfo/meson.build
@@ -25,7 +25,8 @@ 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--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..d2cea79faa7
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.2--1.3.sql
@@ -0,0 +1,12 @@
+/* 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_supported_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_supported_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_shared_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_shared_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/sslinfo/sslinfo--1.3.sql b/contrib/sslinfo/sslinfo--1.3.sql
new file mode 100644
index 00000000000..98b51900959
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.3.sql
@@ -0,0 +1,56 @@
+/* contrib/sslinfo/sslinfo--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION sslinfo" to load this file. \quit
+
+CREATE FUNCTION ssl_client_serial() RETURNS numeric
+AS 'MODULE_PATHNAME', 'ssl_client_serial'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_is_used() RETURNS boolean
+AS 'MODULE_PATHNAME', 'ssl_is_used'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_version() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_version'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_cipher() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_cipher'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_cert_present() RETURNS boolean
+AS 'MODULE_PATHNAME', 'ssl_client_cert_present'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_dn_field(text) RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_client_dn_field'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_issuer_field(text) RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_issuer_field'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_dn() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_client_dn'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_issuer_dn() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_issuer_dn'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_supported_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_supported_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_shared_groups() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_shared_groups'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION
+ssl_extension_info(OUT name text,
+ OUT value text,
+ OUT critical boolean
+) RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'ssl_extension_info'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 2b9eb90b093..3efdca6d6bc 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -88,6 +88,37 @@ ssl_cipher(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
+PG_FUNCTION_INFO_V1(ssl_supported_groups);
+Datum
+ssl_supported_groups(PG_FUNCTION_ARGS)
+{
+ const char *groups;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ groups = be_tls_get_supported_groups(MyProcPort);
+ if (groups == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(groups));
+}
+
+PG_FUNCTION_INFO_V1(ssl_shared_groups);
+Datum
+ssl_shared_groups(PG_FUNCTION_ARGS)
+{
+ const char *groups;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ groups = be_tls_get_shared_groups(MyProcPort);
+ if (groups == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(groups));
+}
/*
* Indicates whether current client provided a certificate
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/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 4da6ac22ff9..b134fc53f5f 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -51,6 +51,8 @@
#endif
#include <openssl/x509v3.h>
+#define SSL_SUPPORTED_GROUPS 0
+#define SSL_SHARED_GROUPS 1
/* default init hook can be overridden by a shared library */
static void default_openssl_tls_init(SSL_CTX *context, bool isServerStart);
@@ -1560,6 +1562,62 @@ be_tls_get_cipher(Port *port)
return NULL;
}
+static const char *
+be_tls_get_groups_internal(Port *port, int type)
+{
+ if (port->ssl)
+ {
+ int i, ngroups, *groups, nid;
+ StringInfoData str;
+
+ if (type == SSL_SUPPORTED_GROUPS)
+ ngroups = SSL_get1_groups(port->ssl, NULL);
+ else
+ ngroups = SSL_get_shared_group(port->ssl, -1);
+
+ if (ngroups <= 0)
+ return NULL;
+
+ groups = palloc(ngroups * sizeof(*groups));
+ initStringInfo(&str);
+
+ if (type == SSL_SUPPORTED_GROUPS)
+ SSL_get1_groups(port->ssl, groups);
+
+ for (i = 0; i < ngroups; i++)
+ {
+ const char *name;
+
+ if (i)
+ appendStringInfo(&str, ":");
+
+ if (type == SSL_SUPPORTED_GROUPS)
+ nid = groups[i];
+ else
+ nid = SSL_get_shared_group(port->ssl, i);
+
+ name = SSL_group_to_name(port->ssl, nid);
+ appendStringInfoString(&str, ((name != NULL) ? name : "(null)"));
+ }
+
+ return str.data;
+ }
+ else
+ return NULL;
+}
+
+const char *
+be_tls_get_supported_groups(Port *port)
+{
+ return be_tls_get_groups_internal(port, SSL_SUPPORTED_GROUPS);
+}
+
+const char *
+be_tls_get_shared_groups(Port *port)
+{
+ return be_tls_get_groups_internal(port, SSL_SHARED_GROUPS);
+}
+
void
be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
{
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 921b2daa4ff..23e11fcb89d 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -317,6 +317,8 @@ extern ssize_t be_tls_write(Port *port, const void *ptr, size_t len, int *waitfo
extern int be_tls_get_cipher_bits(Port *port);
extern const char *be_tls_get_version(Port *port);
extern const char *be_tls_get_cipher(Port *port);
+extern const char *be_tls_get_supported_groups(Port *port);
+extern const char *be_tls_get_shared_groups(Port *port);
extern void be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len);
extern void be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len);
extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
base-commit: 5b93a5987bd704d2363295eee919eee45f84c286
--
2.52.0
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-23 14:32 Daniel Gustafsson <daniel@yesql.se>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 2 replies; 22+ messages in thread
From: Daniel Gustafsson @ 2026-02-23 14:32 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: pgsql-hackers
> On 19 Feb 2026, at 18:44, Dmitry Dolgov <9erthalion6@gmail.com> wrote:
> I've been experimenting with ssl recently, and found it's useful to have
> more information exposed via contrib/sslinfo, in particular
> ssl_supported_groups and ssl_shared_groups to show TLS groups extension.
In general I'm not opposed to adding more information to sslinfo.
+PG_FUNCTION_INFO_V1(ssl_supported_groups);
+Datum
+ssl_supported_groups(PG_FUNCTION_ARGS)
The supported groups is sent as a TLS extension, and we already have
ssl_extension_info() for listing extension values. Shouldn't this be a part of
that function for consistency?
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.3.sql
@@ -0,0 +1,56 @@
+/* contrib/sslinfo/sslinfo--1.3.sql */
No need to spin a new 1.3 file, the upgrade file from 1.2 to 1.3 is sufficient
since the extension machinery will play the files in order to reach 1.3.
+CREATE FUNCTION ssl_shared_groups() RETURNS text
Why not return this as text[] rather than a delimited string?
The patch also lacks docs updates for the new functions.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-23 15:26 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Daniel Gustafsson <daniel@yesql.se>
1 sibling, 0 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-02-23 15:26 UTC (permalink / raw)
To: Daniel Gustafsson <daniel@yesql.se>; +Cc: pgsql-hackers
> On Mon, Feb 23, 2026 at 03:32:02PM +0100, Daniel Gustafsson wrote:
> +PG_FUNCTION_INFO_V1(ssl_supported_groups);
> +Datum
> +ssl_supported_groups(PG_FUNCTION_ARGS)
> The supported groups is sent as a TLS extension, and we already have
> ssl_extension_info() for listing extension values. Shouldn't this be a part of
> that function for consistency?
Good point, thanks. I'll move the functionality in there.
> --- /dev/null
> +++ b/contrib/sslinfo/sslinfo--1.3.sql
> @@ -0,0 +1,56 @@
> +/* contrib/sslinfo/sslinfo--1.3.sql */
> No need to spin a new 1.3 file, the upgrade file from 1.2 to 1.3 is sufficient
> since the extension machinery will play the files in order to reach 1.3.
>
>
> +CREATE FUNCTION ssl_shared_groups() RETURNS text
> Why not return this as text[] rather than a delimited string?
No particular reason, but I see the point -- going to change it to text[].
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-23 16:48 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Daniel Gustafsson <daniel@yesql.se>
1 sibling, 1 reply; 22+ messages in thread
From: Jacob Champion @ 2026-02-23 16:48 UTC (permalink / raw)
To: Daniel Gustafsson <daniel@yesql.se>; +Cc: Dmitry Dolgov <9erthalion6@gmail.com>; pgsql-hackers
On Mon, Feb 23, 2026 at 6:32 AM Daniel Gustafsson <daniel@yesql.se> wrote:
> +CREATE FUNCTION ssl_shared_groups() RETURNS text
I don't feel strongly about this, so feel free to disregard: Is there
a reason this API is focusing on the shared group list as opposed to
the actual negotiated group in use? The latter seems more widely
useful to me, and it'd match up with ssl_cipher(). (Or maybe we
already have the negotiated group somewhere and I just never noticed?)
--Jacob
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-23 17:58 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 22+ messages in thread
From: Dmitry Dolgov @ 2026-02-23 17:58 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Mon, Feb 23, 2026 at 08:48:02AM -0800, Jacob Champion wrote:
> On Mon, Feb 23, 2026 at 6:32 AM Daniel Gustafsson <daniel@yesql.se> wrote:
> > +CREATE FUNCTION ssl_shared_groups() RETURNS text
>
> I don't feel strongly about this, so feel free to disregard: Is there
> a reason this API is focusing on the shared group list as opposed to
> the actual negotiated group in use? The latter seems more widely
> useful to me, and it'd match up with ssl_cipher(). (Or maybe we
> already have the negotiated group somewhere and I just never noticed?)
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?
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-23 19:22 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 1 reply; 22+ messages in thread
From: Jacob Champion @ 2026-02-23 19:22 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
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.
(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.)
--Jacob
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-27 18:57 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 22+ messages in thread
From: Dmitry Dolgov @ 2026-02-27 18:57 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> 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.
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
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
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
Attachments:
[text/plain] v2-0001-contrib-sslinfo-Add-ssl_group_info.patch (9.0K, ../../qyw7l5ztbqouluctxgbxc2aty43suulka2q4ybpaew4tey7rlw@l66rjb7vxxhk/2-v2-0001-contrib-sslinfo-Add-ssl_group_info.patch)
download | inline diff:
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
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
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
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-02-28 00:51 Jacob Champion <jacob.champion@enterprisedb.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 1 reply; 22+ messages in thread
From: Jacob Champion @ 2026-02-28 00:51 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
On Fri, Feb 27, 2026 at 10:57 AM Dmitry Dolgov <9erthalion6@gmail.com> wrote:
> 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?
Yeah -- as long as the API stays coherent, I have no issue with
expanding sslinfo's capabilities.
> select * from ssl_group_info();
> type | name
> ------------+--------------------
> negotiated | X25519MLKEM768
> shared | X25519MLKEM768
> shared | x25519
> supported | X25519MLKEM768
> supported | x25519
Hmm, I'm developing strong opinions over something I said I didn't
feel strongly about. Sorry...
The type names "negotiated", "shared" and "supported" don't really
tell me much as an end user. I know, as a dev, that "negotiated" is
the one that was chosen, "supported" is what the client provided, and
"shared" is the intersection of the client and server sets. But I
think it'd be good to choose names that are either based on the
official TLS specification, or immediately clear to someone who is not
well-versed in TLS to begin with, as opposed to using OpenSSL's
internal API names.
Also, I feel like this is still missing the server side of the Venn diagram.
Also also: if we later expose a version of this table for the
ciphersuites or other negotiated parameters, is this how we'd want the
table to look? What did you care most about when you were debugging?
--Jacob
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-03-03 10:43 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Jacob Champion <jacob.champion@enterprisedb.com>
0 siblings, 1 reply; 22+ messages in thread
From: Dmitry Dolgov @ 2026-03-03 10:43 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Fri, Feb 27, 2026 at 04:51:40PM -0800, Jacob Champion wrote:
> > select * from ssl_group_info();
> > type | name
> > ------------+--------------------
> > negotiated | X25519MLKEM768
> > shared | X25519MLKEM768
> > shared | x25519
> > supported | X25519MLKEM768
> > supported | x25519
>
> Hmm, I'm developing strong opinions over something I said I didn't
> feel strongly about. Sorry...
No worries, it's a valuable discussion so far :)
> The type names "negotiated", "shared" and "supported" don't really
> tell me much as an end user. I know, as a dev, that "negotiated" is
> the one that was chosen, "supported" is what the client provided, and
> "shared" is the intersection of the client and server sets. But I
> think it'd be good to choose names that are either based on the
> official TLS specification, or immediately clear to someone who is not
> well-versed in TLS to begin with, as opposed to using OpenSSL's
> internal API names.
Naming is hard of course, but my plan was to stick to short names like
those above, and unwrap them in the documentation:
* Supported: list of named groups supported by the client for key
exchange in the form of "supported_groups" extension.
Supported group is the exact terminology used in the TLS spec.
* Shared: lisf of named groups shared with the server side.
This one actually doesn't appear in the spec. The closest name
featured is "selected_groups", but only in the context of the retry
requests. Thus I took this form the OpenSSL docs.
* Negotiated: the group used for the handshake key exchange process.
Surprsingly, I don't see any exact terminology for this in the TLS
spec, it just says "the named group for the key being exchanged". The
name is taken from the OpenSSL documentation.
How does it sound?
> Also, I feel like this is still missing the server side of the Venn diagram.
Indeed, the TLS spec says that the server is permitted to send the
"supported_groups" extension to the client, so this might be
interesting. But it doesn't look like there is any OpenSSL API to get
those, beyond SSL_ctrl, which is not supposed to be called directly.
> Also also: if we later expose a version of this table for the
> ciphersuites or other negotiated parameters, is this how we'd want the
> table to look? What did you care most about when you were debugging?
I don't see why not. The reason why I've started tinkering on that was
only to get the negotiated group. But I'm convinced that, since
PostgreSQL sets the groups extension, it totally makes sense to provide
some API for diagnostig reasons to check what's in there beyond only one
single group.
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-03-18 11:18 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 1 reply; 22+ messages in thread
From: Dmitry Dolgov @ 2026-03-18 11:18 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Tue, Mar 03, 2026 at 11:43:10AM +0100, Dmitry Dolgov wrote:
> Naming is hard of course, but my plan was to stick to short names like
> those above, and unwrap them in the documentation:
>
> * Supported: list of named groups supported by the client for key
> exchange in the form of "supported_groups" extension.
>
> Supported group is the exact terminology used in the TLS spec.
>
> * Shared: lisf of named groups shared with the server side.
>
> This one actually doesn't appear in the spec. The closest name
> featured is "selected_groups", but only in the context of the retry
> requests. Thus I took this form the OpenSSL docs.
>
> * Negotiated: the group used for the handshake key exchange process.
>
> Surprsingly, I don't see any exact terminology for this in the TLS
> spec, it just says "the named group for the key being exchanged". The
> name is taken from the OpenSSL documentation.
>
> How does it sound?
Added those into the documentation, will create a CF item.
From 55e5964de0c2b0326ae721ac0405b46dba7d2927 Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v3] 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 | 45 +++++++
src/tools/pgindent/typedefs.list | 1 +
7 files changed, 225 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..422745de37c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -240,6 +240,51 @@ emailAddress
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ Lisf of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..b5ea3c18291 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2720,6 +2720,7 @@ SQLValueFunction
SQLValueFunctionOp
SSL
SSLExtensionInfoContext
+SSLGroupInfoContext
SSL_CTX
STARTUPINFO
STRLEN
base-commit: e82fc27e095b5a84c578b6e6b43b3396463bd812
--
2.52.0
Attachments:
[text/plain] v3-0001-contrib-sslinfo-Add-ssl_group_info.patch (9.7K, ../../rf5vuhdo4ub2qz3eivighhjs4tfkfclbosptnhzdpzibx5aq6z@faylol54ro5s/2-v3-0001-contrib-sslinfo-Add-ssl_group_info.patch)
download | inline diff:
From 55e5964de0c2b0326ae721ac0405b46dba7d2927 Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v3] 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 | 45 +++++++
src/tools/pgindent/typedefs.list | 1 +
7 files changed, 225 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..422745de37c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -240,6 +240,51 @@ emailAddress
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ Lisf of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..b5ea3c18291 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2720,6 +2720,7 @@ SQLValueFunction
SQLValueFunctionOp
SSL
SSLExtensionInfoContext
+SSLGroupInfoContext
SSL_CTX
STARTUPINFO
STRLEN
base-commit: e82fc27e095b5a84c578b6e6b43b3396463bd812
--
2.52.0
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-03-19 15:15 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 1 reply; 22+ messages in thread
From: Dmitry Dolgov @ 2026-03-19 15:15 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Wed, Mar 18, 2026 at 12:18:52PM +0100, Dmitry Dolgov wrote:
> Added those into the documentation, will create a CF item.
And had to fix one thing right away, the installable version was
missing.
From 65b6abbac2ab18aad0a64bc5e9cd8e9f7b26b797 Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v4] 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 | 1 +
contrib/sslinfo/sslinfo--1.2--1.3.sql | 10 ++
contrib/sslinfo/sslinfo.c | 167 +++++++++++++++++++++++++-
contrib/sslinfo/sslinfo.control | 2 +-
doc/src/sgml/sslinfo.sgml | 45 +++++++
src/tools/pgindent/typedefs.list | 1 +
7 files changed, 225 insertions(+), 3 deletions(-)
create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql
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..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..422745de37c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -240,6 +240,51 @@ emailAddress
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ Lisf of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..b5ea3c18291 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2720,6 +2720,7 @@ SQLValueFunction
SQLValueFunctionOp
SSL
SSLExtensionInfoContext
+SSLGroupInfoContext
SSL_CTX
STARTUPINFO
STRLEN
base-commit: e82fc27e095b5a84c578b6e6b43b3396463bd812
--
2.52.0
Attachments:
[text/plain] v4-0001-contrib-sslinfo-Add-ssl_group_info.patch (9.7K, ../../slgx6krxgs2sb6m7j6z6qht25geaa2jbwwiizwimudrxqxxg3n@2qq5eslriowi/2-v4-0001-contrib-sslinfo-Add-ssl_group_info.patch)
download | inline diff:
From 65b6abbac2ab18aad0a64bc5e9cd8e9f7b26b797 Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v4] 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 | 1 +
contrib/sslinfo/sslinfo--1.2--1.3.sql | 10 ++
contrib/sslinfo/sslinfo.c | 167 +++++++++++++++++++++++++-
contrib/sslinfo/sslinfo.control | 2 +-
doc/src/sgml/sslinfo.sgml | 45 +++++++
src/tools/pgindent/typedefs.list | 1 +
7 files changed, 225 insertions(+), 3 deletions(-)
create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql
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..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..422745de37c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -240,6 +240,51 @@ emailAddress
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ Lisf of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..b5ea3c18291 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2720,6 +2720,7 @@ SQLValueFunction
SQLValueFunctionOp
SSL
SSLExtensionInfoContext
+SSLGroupInfoContext
SSL_CTX
STARTUPINFO
STRLEN
base-commit: e82fc27e095b5a84c578b6e6b43b3396463bd812
--
2.52.0
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-03-21 13:25 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
0 siblings, 2 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-03-21 13:25 UTC (permalink / raw)
To: Jacob Champion <jacob.champion@enterprisedb.com>; +Cc: Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Thu, Mar 19, 2026 at 04:15:35PM +0100, Dmitry Dolgov wrote:
> > On Wed, Mar 18, 2026 at 12:18:52PM +0100, Dmitry Dolgov wrote:
> > Added those into the documentation, will create a CF item.
>
> And had to fix one thing right away, the installable version was
> missing.
Looks like the functions required for this are not available on some
supported versions of openssl and some platforms, so I've expanded the
patch to verify their presence. A somewhat confusing part is that some
of the API functions are real functions and some are just wrappers, for
the latter the declaration has to be checked.
As a note, when I was testing it, for some reason autoconf 2.69 was
moving few unrelevant blocks around without changing anything. I've
tried to avoid those, hopefully it worked out just fine.
From 19647a6a7a235bb27b23481660b9d5aebbdff2fd Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v5] 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.
---
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 | 189 +++++++++++++++++++++++++-
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, 303 insertions(+), 3 deletions(-)
create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql
diff --git a/configure b/configure
index 5aec0afa9ab..b867c188ffc 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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>
+"
+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 fead9a6ce99..6b3bd8667bc 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])
+ # 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 <openssl/ssl.h>])
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..d3f50bc03bf 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -19,6 +19,11 @@
#include "miscadmin.h"
#include "utils/builtins.h"
+#define HAVE_SSL_GROUPS \
+ defined(HAVE_DECL_SSL_GET1_GROUPS) && \
+ defined(HAVE_DECL_SSL_GET_NEGOTIATED_GROUP) && \
+ defined(HAVE_SSL_GROUP_TO_NAME)
+
PG_MODULE_MAGIC_EXT(
.name = "sslinfo",
.version = PG_VERSION
@@ -28,13 +33,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 +494,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)
+{
+#if 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);
+ }
+
+#if 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)
+ {
+#if 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..422745de37c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -240,6 +240,51 @@ emailAddress
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ Lisf of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/meson.build b/meson.build
index 7ee900198db..944db2610b4 100644
--- a/meson.build
+++ b/meson.build
@@ -1684,6 +1684,9 @@ if sslopt in ['auto', 'openssl']
['X509_get_signature_info'],
['SSL_CTX_set_num_tickets'],
['SSL_CTX_set_keylog_callback'],
+
+ # Function for groups support.
+ ['SSL_group_to_name'],
]
are_openssl_funcs_complete = true
@@ -2793,6 +2796,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 79379a4d125..a59373ae7ad 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
@@ -378,6 +386,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 <stdint.h> header file. */
#undef HAVE_STDINT_H
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..b5ea3c18291 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2720,6 +2720,7 @@ SQLValueFunction
SQLValueFunctionOp
SSL
SSLExtensionInfoContext
+SSLGroupInfoContext
SSL_CTX
STARTUPINFO
STRLEN
base-commit: e82fc27e095b5a84c578b6e6b43b3396463bd812
--
2.52.0
Attachments:
[text/plain] v5-0001-contrib-sslinfo-Add-ssl_group_info.patch (14.2K, ../../tuobeew3dd3eubtelwroocwgac3jhlselwshs2km5ovei77tao@wghkpdvvthbi/2-v5-0001-contrib-sslinfo-Add-ssl_group_info.patch)
download | inline diff:
From 19647a6a7a235bb27b23481660b9d5aebbdff2fd Mon Sep 17 00:00:00 2001
From: Dmitrii Dolgov <9erthalion6@gmail.com>
Date: Thu, 19 Feb 2026 16:33:17 +0100
Subject: [PATCH v5] 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.
---
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 | 189 +++++++++++++++++++++++++-
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, 303 insertions(+), 3 deletions(-)
create mode 100644 contrib/sslinfo/sslinfo--1.2--1.3.sql
diff --git a/configure b/configure
index 5aec0afa9ab..b867c188ffc 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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>
+"
+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 fead9a6ce99..6b3bd8667bc 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])
+ # 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 <openssl/ssl.h>])
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..d3f50bc03bf 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -19,6 +19,11 @@
#include "miscadmin.h"
#include "utils/builtins.h"
+#define HAVE_SSL_GROUPS \
+ defined(HAVE_DECL_SSL_GET1_GROUPS) && \
+ defined(HAVE_DECL_SSL_GET_NEGOTIATED_GROUP) && \
+ defined(HAVE_SSL_GROUP_TO_NAME)
+
PG_MODULE_MAGIC_EXT(
.name = "sslinfo",
.version = PG_VERSION
@@ -28,13 +33,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 +494,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)
+{
+#if 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);
+ }
+
+#if 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)
+ {
+#if 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..422745de37c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -240,6 +240,51 @@ emailAddress
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ Lisf of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/meson.build b/meson.build
index 7ee900198db..944db2610b4 100644
--- a/meson.build
+++ b/meson.build
@@ -1684,6 +1684,9 @@ if sslopt in ['auto', 'openssl']
['X509_get_signature_info'],
['SSL_CTX_set_num_tickets'],
['SSL_CTX_set_keylog_callback'],
+
+ # Function for groups support.
+ ['SSL_group_to_name'],
]
are_openssl_funcs_complete = true
@@ -2793,6 +2796,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 79379a4d125..a59373ae7ad 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
@@ -378,6 +386,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 <stdint.h> header file. */
#undef HAVE_STDINT_H
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..b5ea3c18291 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2720,6 +2720,7 @@ SQLValueFunction
SQLValueFunctionOp
SSL
SSLExtensionInfoContext
+SSLGroupInfoContext
SSL_CTX
STARTUPINFO
STRLEN
base-commit: e82fc27e095b5a84c578b6e6b43b3396463bd812
--
2.52.0
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-05-08 21:36 Cary Huang <cary.huang@highgo.ca>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
1 sibling, 1 reply; 22+ messages in thread
From: Cary Huang @ 2026-05-08 21:36 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
Hi
Given that sslinfo is designed to expose diagnostic information
about the current TLS connection, I am supportive of extending
its functionality.
Just some comments about the patch:
> /* 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)));
It is possible that SSL_get_negotiated_group() and
SSL_get_shared_group() would return NID_undef when there is no
negotiated group. The current code will pass that to
SSL_group_to_name() and raise an error if it returns NULL.
Instead of failing the whole function, would it be better to
just omit that row since the function returns a SETOF record?
if nid == NID_undef, we could just omit the row instead of
making a call to SSL_group_to_name(), which most likely will
fail.
Also, I found a small typo on documentation:
> Lisf of named groups shared with the server side.
should be corrected to:
List of named groups shared with the server side.
thanks!
Cary Huang
-------------
HighGo Software Inc. (Canada)
cary.huang@highgo.ca
www.highgo.ca
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-05-11 15:50 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Cary Huang <cary.huang@highgo.ca>
0 siblings, 0 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-05-11 15:50 UTC (permalink / raw)
To: Cary Huang <cary.huang@highgo.ca>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Fri, May 08, 2026 at 02:36:10PM -0700, Cary Huang wrote:
> Hi
>
> Given that sslinfo is designed to expose diagnostic information
> about the current TLS connection, I am supportive of extending
> its functionality.
Thanks for looking into it.
> > /* 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)));
>
> It is possible that SSL_get_negotiated_group() and
> SSL_get_shared_group() would return NID_undef when there is no
> negotiated group. The current code will pass that to
> SSL_group_to_name() and raise an error if it returns NULL.
>
> Instead of failing the whole function, would it be better to
> just omit that row since the function returns a SETOF record?
>
> if nid == NID_undef, we could just omit the row instead of
> making a call to SSL_group_to_name(), which most likely will
> fail.
It makes sense to me in general, but it looks there are some arguments
against this:
* ssl_extension_info function is also an SRF and returns an error if
faced NID_undef. It's probably a good idea to be concistent with the
existing functionality.
* From what I see SRF API doesn't allow to "skip" a record, available
options are either to finish the set with SRF_RETURN_DONE, or to
return a NULL record with SRF_RETURN_NEXT_NULL. That means that when
facing NID_undef, we can stop altogether and skip all the records
after this, or have a NULL record in the output, both don't sound
fitting the purpose here.
With this in mind I'm inclined to leave it as it is, but open for
suggestions.
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-05-12 21:48 Zsolt Parragi <zsolt.parragi@percona.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
1 sibling, 1 reply; 22+ messages in thread
From: Zsolt Parragi @ 2026-05-12 21:48 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
Hello!
+#define HAVE_SSL_GROUPS \
+ defined(HAVE_DECL_SSL_GET1_GROUPS) && \
+ defined(HAVE_DECL_SSL_GET_NEGOTIATED_GROUP) && \
+ defined(HAVE_SSL_GROUP_TO_NAME)
I don't think this check works properly
1. autoconf/meson always defines HAVE_DECL to 0/1, so defined() always
returns true
2. in practice it should work, but using defined() in a macro
expansion is undefined behavior
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-05-13 18:08 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Zsolt Parragi <zsolt.parragi@percona.com>
0 siblings, 2 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-05-13 18:08 UTC (permalink / raw)
To: Zsolt Parragi <zsolt.parragi@percona.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers
> On Tue, May 12, 2026 at 10:48:20PM +0100, Zsolt Parragi wrote:
> +#define HAVE_SSL_GROUPS \
> + defined(HAVE_DECL_SSL_GET1_GROUPS) && \
> + defined(HAVE_DECL_SSL_GET_NEGOTIATED_GROUP) && \
> + defined(HAVE_SSL_GROUP_TO_NAME)
>
> I don't think this check works properly
> 1. autoconf/meson always defines HAVE_DECL to 0/1, so defined() always
> returns true
> 2. in practice it should work, but using defined() in a macro
> expansion is undefined behavior
That's indeed a faux pas. I was testing this on openssl 1.1.1w, which
was lacking both SSL_get_negotiated_group and SSL_group_to_name, and
this was hiding the problem. The new version should address this
problem, and includes documentation fix from Cary.
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 <daniel@yesql.se>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Cary Huang <cary.huang@highgo.ca>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
---
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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>])
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
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ List of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
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 <stdint.h> 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
Attachments:
[text/plain] v6-0001-contrib-sslinfo-Add-ssl_group_info.patch (14.5K, ../../uy2ho4qzwzsm5zqi2s3po4iesgvzfybdq6vldqpsldeot6xkyn@repwtjoc63ok/2-v6-0001-contrib-sslinfo-Add-ssl_group_info.patch)
download | inline diff:
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 <daniel@yesql.se>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Cary Huang <cary.huang@highgo.ca>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
---
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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>])
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
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ List of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
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 <stdint.h> 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
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-08-31 16:37 Si, Evan <evansi.dev@gmail.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
1 sibling, 1 reply; 22+ messages in thread
From: Si, Evan @ 2026-08-31 16:37 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers; Zsolt Parragi <zsolt.parragi@percona.com>
On 5/13/26 11:08 AM, Dmitry Dolgov wrote:
> The new version should address this problem, and includes documentation fix from Cary.
Hi Dmitry,
I like having some way to check the negotiated group akin to
ssl_cipher(), though I am less sure about the utility of displaying
groups that weren't negotiated (as noted upthread).
Assuming we still want to keep those details, I feel the naming could be
a bit clearer. "supported_groups" makes sense in the context of the
handshake, but having the server tell a client a value is "supported"
feels like its saying something about itself rather than the client.
Maybe "client_supported" is more informative?
Regardless, if RSA key exchange is used, SSL_get_negotiated_group is
supposed to return NID_undef. Things will error (Openssl 3.5 example):
postgres=# ALTER SYSTEM SET ssl_ciphers='TLS_RSA_WITH_AES_128_CBC_SHA256';
(...)
PGSSLMAXPROTOCOLVERSION=TLSv1.2 psql "host=localhost dbname=postgres
user=evsi sslmode=require"
(...)
postgres=# SELECT ssl_group_info();
2026-08-29 20:58:41.172 UTC [60034] ERROR: unknown OpenSSL group at
position 0
2026-08-29 20:58:41.172 UTC [60034] STATEMENT: SELECT ssl_group_info();
I feel that listing the (lack of) a negotiated group is the right thing
to do in this case.
Evan
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-09-04 14:35 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Si, Evan <evansi.dev@gmail.com>
0 siblings, 2 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-09-04 14:35 UTC (permalink / raw)
To: Si, Evan <evansi.dev@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers; Zsolt Parragi <zsolt.parragi@percona.com>
> On Mon, Aug 31, 2026 at 09:37:39AM -0700, Si, Evan wrote:
>
> Assuming we still want to keep those details, I feel the naming could be a
> bit clearer. "supported_groups" makes sense in the context of the handshake,
> but having the server tell a client a value is "supported" feels like its
> saying something about itself rather than the client. Maybe
> "client_supported" is more informative?
Fair enough. It's already described in the documentation as "groups
supported by the client", but yeah, still could be confusing and a more
verbose column name can help.
> Regardless, if RSA key exchange is used, SSL_get_negotiated_group is
> supposed to return NID_undef. Things will error (Openssl 3.5 example):
Interesting, good to know, thanks. After a quick look I couldn't find
any documentation as to why it's happening this way, I only see OpenSSL
returning NID_undef if using tls1.2 and the ssl state has no session. Is
there any explanation?
In any case that gives a new spin to what Cary was mentioning few
messages above. If we have a valid use case where there is no negotiated
group, then SRF has to return NULL for those rows. To still have a nice
looking ssl_group_info function, I'll create another one called
ssl_group_info_with_nulls, so that ssl_group_info would be a wrapper
around it to filter out nulls.
As an interesting side note, I was looking at the possible error codes
for a warning in this scenario, and stumbled upon one called
ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION
which seems to be never used, maybe it's worth removing it.
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-09-04 23:20 Si, Evan <evansi.dev@gmail.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
1 sibling, 0 replies; 22+ messages in thread
From: Si, Evan @ 2026-09-04 23:20 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers; Zsolt Parragi <zsolt.parragi@percona.com>
On 9/4/26 7:35 AM, Dmitry Dolgov wrote:
>> On Mon, Aug 31, 2026 at 09:37:39AM -0700, Si, Evan wrote:
>>
>> Regardless, if RSA key exchange is used, SSL_get_negotiated_group is
>> supposed to return NID_undef. Things will error (Openssl 3.5 example):
>
> Interesting, good to know, thanks. After a quick look I couldn't find
> any documentation as to why it's happening this way, I only see OpenSSL
> returning NID_undef if using tls1.2 and the ssl state has no session. Is
> there any explanation?
The documentation doesn't look terribly clear about this to me either,
but I think its sensible. In the RSA case, there is no negotiation for
the key, so getting undef out of "SSL_get_negotiated_group" sounds
reasonable.
Poking around a bit more, there is some further nuance for non-EC DHE
key exchange (e.g. ssl_ciphers=DHE-RSA-AES128-GCM-SHA256). The server
always passes the FILE_DH2048 (or content of ssl_dh_params_file) into
SSL_CTX_set_tmp_dh. This case would also mean there is no negotiation
happening.
In TLSv1.3 there's no more support for custom dh though, so from my
testing this SSL_CTX_set_tmp_dh is completely ignored there and things
work fine (it has to all go through
ssl_groups/SSL_CTX_set1_groups_list). In other words only for TLSv1.2
and lower, non-EC DHE key exchange has no negotiation and thus NID_undef
comes out of the API.
Evan
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-09-06 09:04 Zsolt Parragi <zsolt.parragi@percona.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
1 sibling, 1 reply; 22+ messages in thread
From: Zsolt Parragi @ 2026-09-06 09:04 UTC (permalink / raw)
To: Dmitry Dolgov <9erthalion6@gmail.com>; +Cc: pgsql-hackers@lists.postgresql.org, Jacob Champion <jacob.champion@enterprisedb.com>
+ /*
+ * 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)));
I'm not sure if erroring out is the proper approach here. Won't this
mean that with a newer openssl version on the client side, the
function will potentially simply error out instead of returning proper
results?
For example, we could report
values[1] = StringGetTextDatum(psprintf("unknown (0x%04x)", nid & 0xffff));
in this case?
@@ -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'],
This list isn't OpenSSL-aware, these won't get proper dependencies,
these should go inside the ssl.found() block
+#else
+ /* SSL groups API is not present, skip */
+ MemoryContextSwitchTo(oldcontext);
+ SRF_RETURN_DONE(funcctx);
+#endif
Shouldn't this branch have some documentation / diagnostics so it's
not a completely silent/undocumented skip?
And in the commit message:
select * from ssl_group_info();
type | name
------------+--------------------
That seems to be at typo/earlier leftover as it should be group_type now?
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-09-08 15:12 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Zsolt Parragi <zsolt.parragi@percona.com>
0 siblings, 0 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-09-08 15:12 UTC (permalink / raw)
To: Zsolt Parragi <zsolt.parragi@percona.com>; +Cc: pgsql-hackers@lists.postgresql.org, Jacob Champion <jacob.champion@enterprisedb.com>
> On Sun, Sep 06, 2026 at 02:04:01AM -0700, Zsolt Parragi wrote:
> + /*
> + * 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)));
>
> I'm not sure if erroring out is the proper approach here. Won't this
> mean that with a newer openssl version on the client side, the
> function will potentially simply error out instead of returning proper
> results?
In one of the previous messages we've decided to report null rows in SRF
for such scenarios, and filter them out in SQL. I'm about to post the
new version with accumulated changes.
^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add ssl_(supported|shared)_groups to sslinfo
@ 2026-09-08 15:15 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Dmitry Dolgov <9erthalion6@gmail.com>
1 sibling, 0 replies; 22+ messages in thread
From: Dmitry Dolgov @ 2026-09-08 15:15 UTC (permalink / raw)
To: Si, Evan <evansi.dev@gmail.com>; +Cc: Jacob Champion <jacob.champion@enterprisedb.com>; Daniel Gustafsson <daniel@yesql.se>; pgsql-hackers; Zsolt Parragi <zsolt.parragi@percona.com>
> On Fri, Sep 04, 2026 at 04:35:16PM +0200, Dmitry Dolgov wrote:
>
> In any case that gives a new spin to what Cary was mentioning few
> messages above. If we have a valid use case where there is no negotiated
> group, then SRF has to return NULL for those rows. To still have a nice
> looking ssl_group_info function, I'll create another one called
> ssl_group_info_with_nulls, so that ssl_group_info would be a wrapper
> around it to filter out nulls.
Here is what I had in mind. This patch also should include all review
commentaties accumulated so far.
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 <daniel@yesql.se>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Cary Huang <cary.huang@highgo.ca>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Evan Si <evansi.dev@gmail.com>
---
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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>])
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
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ List of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>client supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
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 <stdint.h> 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
Attachments:
[text/plain] v7-0001-contrib-sslinfo-Add-ssl_group_info.patch (15.1K, ../../aqAmBVEWqwNHGHL7@ddolgov-thinkpadt14sgen1.rmtde.csb/2-v7-0001-contrib-sslinfo-Add-ssl_group_info.patch)
download | inline diff:
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 <daniel@yesql.se>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Cary Huang <cary.huang@highgo.ca>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Evan Si <evansi.dev@gmail.com>
---
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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>
+"
+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 <openssl/ssl.h>])
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
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>ssl_group_info() returns setof record</function>
+ <indexterm>
+ <primary>ssl_group_info</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Provide information about TLS groups: group type and group name.
+ The group type value could be one of the following:
+
+ <variablelist>
+ <varlistentry id="ssl-group-info-negotiated">
+ <term><literal>negotiated</literal></term>
+ <listitem>
+ <para>
+ The group used for the handshake key exchange process.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-shared">
+ <term><literal>shared</literal></term>
+ <listitem>
+ <para>
+ List of named groups shared with the server side.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="ssl-group-info-supported">
+ <term><literal>client supported</literal></term>
+ <listitem>
+ <para>
+ list of named groups supported by the client for key exchange in the
+ form of "supported_groups" extension.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
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 <stdint.h> 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
^ permalink raw reply [nested|flat] 22+ messages in thread
end of thread, other threads:[~2026-09-08 15:15 UTC | newest]
Thread overview: 22+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-02-19 17:44 Add ssl_(supported|shared)_groups to sslinfo Dmitry Dolgov <9erthalion6@gmail.com>
2026-02-23 14:32 ` Daniel Gustafsson <daniel@yesql.se>
2026-02-23 15:26 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-02-23 16:48 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-02-23 17:58 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-02-23 19:22 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-02-27 18:57 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-02-28 00:51 ` Jacob Champion <jacob.champion@enterprisedb.com>
2026-03-03 10:43 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-03-18 11:18 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-03-19 15:15 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-03-21 13:25 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-05-08 21:36 ` Cary Huang <cary.huang@highgo.ca>
2026-05-11 15:50 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-05-12 21:48 ` Zsolt Parragi <zsolt.parragi@percona.com>
2026-05-13 18:08 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-08-31 16:37 ` Si, Evan <evansi.dev@gmail.com>
2026-09-04 14:35 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-09-04 23:20 ` Si, Evan <evansi.dev@gmail.com>
2026-09-08 15:15 ` Dmitry Dolgov <9erthalion6@gmail.com>
2026-09-06 09:04 ` Zsolt Parragi <zsolt.parragi@percona.com>
2026-09-08 15:12 ` Dmitry Dolgov <9erthalion6@gmail.com>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox