From 6662750b8e6bc2c9c3fcd5ae727559912a64757d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Renaud=20M=C3=A9trich?= <rmetrich@redhat.com>
Date: Mon, 15 Jun 2026 08:17:29 +0200
Subject: [PATCH v2 5/8] Add compatibility guards for LibreSSL and older
 OpenSSL
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Guard SSL_CTX_set_current_cert() and SSL_CERT_SET_FIRST/NEXT usage
with #ifdef SSL_CERT_SET_FIRST, since these are available in OpenSSL
1.0.2+ but not in LibreSSL.

When not available:
- ssl_alt_cert_file produces a clear configuration error
- ssl_update_ssl() falls back to the original single-cert copy
- cert types caching is skipped (ssl_server_cert_types() returns NULL)

Author: Renaud Métrich <rmetrich@redhat.com>
---
 src/backend/libpq/be-secure-openssl.c | 36 +++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 6964830c5af..2b69be6b46b 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -558,6 +558,7 @@ be_tls_init(bool isServerStart)
 	 * contexts will be freed when PostmasterContext is deleted.
 	 */
 	ssl_cert_types_cached[0] = '\0';
+#ifdef SSL_CERT_SET_FIRST
 	if (new_hosts->default_host && new_hosts->default_host->ssl_ctx)
 	{
 		SSL_CTX    *cache_ctx = new_hosts->default_host->ssl_ctx;
@@ -593,6 +594,7 @@ be_tls_init(bool isServerStart)
 
 		ssl_cert_types_cached[cpos] = '\0';
 	}
+#endif								/* SSL_CERT_SET_FIRST */
 
 	/*
 	 * Success!  Replace any existing SSL_context and host configurations.
@@ -787,7 +789,11 @@ init_host_context(HostsLine *host, bool isServerStart, bool is_default)
 	 * the appropriate one during the TLS handshake.  Only load for the
 	 * default host context (postgresql.conf), not for per-host SNI entries
 	 * from pg_hosts.conf.
+	 *
+	 * Requires SSL_CTX_set_current_cert() for same-type detection, which
+	 * is available in OpenSSL 1.0.2+ but not in LibreSSL.
 	 */
+#ifdef SSL_CERT_SET_FIRST
 	if (is_default && ssl_alt_cert_file[0] && !ssl_alt_key_file[0])
 	{
 		ereport(isServerStart ? FATAL : LOG,
@@ -868,6 +874,16 @@ init_host_context(HostsLine *host, bool isServerStart, bool is_default)
 			}
 		}
 	}
+#else
+	if (is_default && ssl_alt_cert_file[0])
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("ssl_alt_cert_file is not supported by this build"),
+				 errhint("Alternate certificate support requires OpenSSL 1.0.2 or later.")));
+		goto error;
+	}
+#endif								/* SSL_CERT_SET_FIRST */
 
 	/*
 	 * Load CA store, so we can verify client certificates if needed.
@@ -1970,7 +1986,11 @@ ssl_update_ssl(SSL *ssl, HostsLine *host_config)
 	 * and ECDSA) and copy each onto the per-connection SSL object.
 	 * SSL_use_cert_and_key with override=1 replaces; override=0 adds a
 	 * cert for a different key type without wiping existing ones.
+	 *
+	 * Fall back to single-cert copy when SSL_CTX_set_current_cert() is
+	 * not available (LibreSSL).
 	 */
+#ifdef SSL_CERT_SET_FIRST
 	SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST);
 	do
 	{
@@ -1991,6 +2011,22 @@ ssl_update_ssl(SSL *ssl, HostsLine *host_config)
 		}
 		first = false;
 	} while (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT));
+#else
+	cert = SSL_CTX_get0_certificate(ctx);
+	key = SSL_CTX_get0_privatekey(ctx);
+
+	Assert(cert && key);
+
+	if (!SSL_CTX_get0_chain_certs(ctx, &chain)
+		|| !SSL_use_cert_and_key(ssl, cert, key, chain, 1))
+	{
+		ereport(COMMERROR,
+				errcode(ERRCODE_INTERNAL_ERROR),
+				errmsg_internal("could not update certificate chain: %s",
+								SSLerrmessage(ERR_get_error())));
+		return false;
+	}
+#endif
 
 	if (!SSL_check_private_key(ssl))
 	{
-- 
2.52.0

