From 724917cd546d24ac99f3d07c87c2c29a36884616 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Renaud=20M=C3=A9trich?= <rmetrich@redhat.com>
Date: Mon, 15 Jun 2026 09:41:58 +0200
Subject: [PATCH v2 8/8] Expand test coverage for dual certificate support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add tests for:
- Same-type alt cert rejection (both RSA -> startup error)
- Single cert mode regression (no alt cert still works)
- SIGHUP reload adds alt cert (ECDSA becomes available)
- SIGHUP reload removes alt cert (ECDSA no longer available)

Remove the global ssl_max_protocol_version=TLSv1.2 restriction so
TLS 1.3 connections are actually exercised (the openssl s_client
tests already force -tls1_2 explicitly).

Refactor config rewriting into a write_sslconfig() helper.

29 subtests total, up from 22.

Author: Renaud Métrich <rmetrich@redhat.com>
---
 src/test/ssl/t/004_ssl_alt_cert.pl | 111 ++++++++++++++++++++++-------
 1 file changed, 86 insertions(+), 25 deletions(-)

diff --git a/src/test/ssl/t/004_ssl_alt_cert.pl b/src/test/ssl/t/004_ssl_alt_cert.pl
index a148e0a7fb1..b4973bfc996 100644
--- a/src/test/ssl/t/004_ssl_alt_cert.pl
+++ b/src/test/ssl/t/004_ssl_alt_cert.pl
@@ -77,10 +77,6 @@ $node->append_conf('sslconfig.conf',
 $node->append_conf('sslconfig.conf',
 	"ssl_alt_key_file='$ecdsa_key'");
 
-# Force TLS 1.2 so we can control cipher selection
-$node->append_conf('sslconfig.conf',
-	"ssl_max_protocol_version='TLSv1.2'");
-
 $node->restart;
 
 #### Tests.
@@ -146,16 +142,34 @@ $result = $node->safe_psql('trustdb',
 like($result, qr/RSA/, 'ssl_server_cert_types() includes RSA');
 like($result, qr/ECDSA/, 'ssl_server_cert_types() includes ECDSA');
 
+# Helper to rewrite sslconfig.conf from scratch
+sub write_sslconfig
+{
+	my ($node, %opts) = @_;
+	my $conf = $node->data_dir . '/sslconfig.conf';
+	unlink($conf);
+	$node->append_conf('sslconfig.conf', "ssl=on");
+	$node->append_conf('sslconfig.conf',
+		"ssl_ca_file='root+client_ca.crt'");
+	$node->append_conf('sslconfig.conf',
+		"ssl_cert_file='server-cn-only.crt'");
+	$node->append_conf('sslconfig.conf',
+		"ssl_key_file='server-cn-only.key'");
+	foreach my $key (sort keys %opts)
+	{
+		$node->append_conf('sslconfig.conf', "$key=$opts{$key}");
+	}
+}
+
 # Test 8: Verify server rejects mismatched alt cert/key configuration
 note "testing server rejects ssl_alt_cert_file without ssl_alt_key_file";
 
-$node->append_conf('sslconfig.conf',
-	"ssl_alt_key_file=''");
+write_sslconfig($node,
+	ssl_alt_cert_file => "'$ecdsa_crt'");
 
 $result = $node->restart(fail_ok => 1);
 is($result, 0, 'restart fails with ssl_alt_cert_file set but ssl_alt_key_file empty');
 
-# Check the log for the expected error message
 my $log = slurp_file($node->logfile);
 like($log, qr/ssl_alt_cert_file is set but ssl_alt_key_file is not/,
 	'log contains expected error for missing ssl_alt_key_file');
@@ -163,16 +177,8 @@ like($log, qr/ssl_alt_cert_file is set but ssl_alt_key_file is not/,
 # Test 9: Verify the reverse mismatch (key without cert)
 note "testing server rejects ssl_alt_key_file without ssl_alt_cert_file";
 
-ok(unlink($node->data_dir . '/sslconfig.conf'));
-$node->append_conf('sslconfig.conf', "ssl=on");
-$node->append_conf('sslconfig.conf',
-	"ssl_ca_file='root+client_ca.crt'");
-$node->append_conf('sslconfig.conf',
-	"ssl_cert_file='server-cn-only.crt'");
-$node->append_conf('sslconfig.conf',
-	"ssl_key_file='server-cn-only.key'");
-$node->append_conf('sslconfig.conf',
-	"ssl_alt_key_file='$ecdsa_key'");
+write_sslconfig($node,
+	ssl_alt_key_file => "'$ecdsa_key'");
 
 $result = $node->restart(fail_ok => 1);
 is($result, 0, 'restart fails with ssl_alt_key_file set but ssl_alt_cert_file empty');
@@ -181,15 +187,70 @@ $log = slurp_file($node->logfile);
 like($log, qr/ssl_alt_key_file is set but ssl_alt_cert_file is not/,
 	'log contains expected error for missing ssl_alt_cert_file');
 
-# Restore valid config so the node can be stopped cleanly
-ok(unlink($node->data_dir . '/sslconfig.conf'));
-$node->append_conf('sslconfig.conf', "ssl=on");
-$node->append_conf('sslconfig.conf',
-	"ssl_ca_file='root+client_ca.crt'");
+# Test 10: Verify server rejects same-type alt cert (both RSA)
+note "testing server rejects same-type alternate certificate";
+
+write_sslconfig($node,
+	ssl_alt_cert_file => "'server-cn-only.crt'",
+	ssl_alt_key_file => "'server-cn-only.key'");
+
+$result = $node->restart(fail_ok => 1);
+is($result, 0, 'restart fails with same-type alt cert');
+
+$log = slurp_file($node->logfile);
+like($log, qr/alternate certificate has the same key type/,
+	'log contains expected error for same-type alt cert');
+
+# Test 11: Verify single cert still works (no alt cert regression)
+note "testing single cert mode (no alt cert)";
+
+write_sslconfig($node);
+$node->start;
+
+$node->connect_ok(
+	"$common_connstr sslcert=invalid",
+	"connect with single RSA cert (no alt cert)",
+	sql => "SELECT 1");
+
+$result = $node->safe_psql('trustdb',
+	"SELECT ssl_server_cert_type()",
+	connstr => "$common_connstr sslcert=invalid");
+is($result, 'RSA', 'ssl_server_cert_type() returns RSA with single cert');
+
+# Test 12: Verify SIGHUP reload adds alt cert
+note "testing SIGHUP reload adds alt certificate";
+
 $node->append_conf('sslconfig.conf',
-	"ssl_cert_file='server-cn-only.crt'");
+	"ssl_alt_cert_file='$ecdsa_crt'");
 $node->append_conf('sslconfig.conf',
-	"ssl_key_file='server-cn-only.key'");
-$node->start;
+	"ssl_alt_key_file='$ecdsa_key'");
+$node->reload;
+
+# Wait briefly for reload to take effect
+sleep(1);
+
+# Verify ECDSA cipher now works after reload
+my $openssl_after_reload = `echo "QUIT" | openssl s_client -connect $SERVERHOSTADDR:${\$node->port} -starttls postgres -tls1_2 -cipher ECDHE-ECDSA-AES256-GCM-SHA384 2>&1`;
+like($openssl_after_reload, qr/ECDHE-ECDSA-AES256-GCM-SHA384/,
+	'ECDSA cipher works after SIGHUP reload');
+
+# Test 13: Verify SIGHUP reload removes alt cert
+note "testing SIGHUP reload removes alt certificate";
+
+write_sslconfig($node);
+$node->reload;
+
+sleep(1);
+
+# Verify ECDSA cipher no longer works after removing alt cert
+my $openssl_after_remove = `echo "QUIT" | openssl s_client -connect $SERVERHOSTADDR:${\$node->port} -starttls postgres -tls1_2 -cipher ECDHE-ECDSA-AES256-GCM-SHA384 2>&1`;
+unlike($openssl_after_remove, qr/ECDHE-ECDSA-AES256-GCM-SHA384/,
+	'ECDSA cipher fails after alt cert removed via SIGHUP');
+
+# RSA still works
+$node->connect_ok(
+	"$common_connstr sslcert=invalid",
+	"RSA connection works after alt cert removed via SIGHUP",
+	sql => "SELECT 1");
 
 done_testing();
-- 
2.52.0

