From 77dd311e8e4103ad3f3dde72ce92ea7f890d8431 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v50 02/10] Refactor SSL testharness for multiple library

The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.

No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
 src/test/ssl/t/001_ssltests.pl                |  57 +--------
 src/test/ssl/t/002_scram.pl                   |   4 +-
 src/test/ssl/t/SSL/Backend/OpenSSL.pm         | 110 ++++++++++++++++++
 .../ssl/t/{SSLServer.pm => SSL/Server.pm}     |  92 ++++++++++++---
 4 files changed, 195 insertions(+), 68 deletions(-)
 create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
 rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (77%)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 779ab66838..fbd25a99f1 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
-use File::Copy;
-
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_ssl} ne 'openssl')
 {
@@ -36,39 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
 
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes to using keys stored in a temporary path for the rest of
-# the tests. To get the full path for inclusion in connection strings, the
-# %key hash can be interrogated.
-my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
-my %key;
-my @keys = (
-	"client.key",               "client-revoked.key",
-	"client-der.key",           "client-encrypted-pem.key",
-	"client-encrypted-der.key", "client-dn.key");
-foreach my $keyfile (@keys)
-{
-	copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
-	  or die
-	  "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
-	chmod 0600, "$cert_tempdir/$keyfile"
-	  or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
-	$key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
-	$key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
-}
-
-# Also make a copy of that explicitly world-readable.  We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
-  or die
-  "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
-chmod 0644, "$cert_tempdir/client_wrongperms.key"
-  or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
-$key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
-$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
 #### Set up the server.
 
 note "setting up data directory";
@@ -83,32 +48,22 @@ $node->start;
 
 # Run this before we lock down access below.
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
 
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	'trust');
 
 note "testing password-protected keys";
 
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				   'server-password', 'echo wrongpassword');
 command_fails(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart fails with password-protected key file with wrong password');
 $node->_update_pid(0);
 
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				'server-password', 'echo secret1');
 command_ok(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart succeeds with password-protected key file');
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1c14bb62e6..247887bb56 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_ssl} ne 'openssl')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	plan skip_all => 'SSL not supported by this build';
 }
 
 # This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..991953bf2c
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,110 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA       = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend get_openssl_key);
+
+our (%key);
+
+INIT
+{
+}
+
+sub new
+{
+	my ($class) = @_;
+
+	my $self = { _library => 'OpenSSL' };
+
+	bless $self, $class;
+
+	return $self;
+}
+
+sub get_new_openssl_backend
+{
+	my $class = 'SSL::Backend::OpenSSL';
+
+	my $backend = $class->new();
+
+	return $backend;
+}
+
+sub init
+{
+	# The client's private key must not be world-readable, so take a copy
+	# of the key stored in the code tree and update its permissions.
+	#
+	# This changes to using keys stored in a temporary path for the rest of
+	# the tests. To get the full path for inclusion in connection strings, the
+	# %key hash can be interrogated.
+	my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
+	my @keys = (
+		"client.key",               "client-revoked.key",
+		"client-der.key",           "client-encrypted-pem.key",
+		"client-encrypted-der.key", "client-dn.key");
+	foreach my $keyfile (@keys)
+	{
+		copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
+		  or die
+		  "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
+		chmod 0600, "$cert_tempdir/$keyfile"
+		  or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
+		$key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
+		$key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+	}
+
+	# Also make a copy of that explicitly world-readable.  We can't
+	# necessarily rely on the file in the source tree having those
+	# permissions.
+	copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
+	  or die
+	  "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
+	chmod 0644, "$cert_tempdir/client_wrongperms.key"
+	  or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
+	$key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
+	$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+}
+
+sub get_openssl_key
+{
+	my $self = $_[0];
+	my $keyfile = $_[1];
+
+	return $key{$keyfile};
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+	my $self     = $_[0];
+	my $certfile = $_[1];
+	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || $certfile;
+
+	my $sslconf =
+	    "ssl_ca_file='$cafile.crt'\n"
+	  . "ssl_cert_file='$certfile.crt'\n"
+	  . "ssl_key_file='$keyfile.key'\n"
+	  . "ssl_crl_file='root+client.crl'\n";
+
+	return $sslconf;
+}
+
+sub get_library
+{
+	my ($self) = @_;
+
+	return $self->{_library};
+}
+
+sub cleanup
+{
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 77%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 3d7ff40b97..3683862399 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,20 +26,41 @@
 # explicitly because an invalid sslcert or sslrootcert, respectively,
 # causes those to be ignored.)
 
-package SSLServer;
+package SSL::Server;
 
 use strict;
 use warnings;
 use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
 use File::Basename;
 use File::Copy;
 use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+	$backend = get_new_openssl_backend();
+	$openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+	$backend = get_new_nss_backend();
+	$nss     = 1;
+}
 
 use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
+  set_server_cert
   switch_server_cert
+  key
 );
 
 # Copy a set of files, taking into account wildcards
@@ -58,6 +79,14 @@ sub copy_files
 	return;
 }
 
+sub key
+{
+	my ($node, $keyfile) = @_;
+
+	return get_openssl_key($keyfile) if (defined($openssl);
+	return get_nss_key($keyfile) if (defined($nss));
+}
+
 # serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
 # servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
 sub configure_test_server_for_ssl
@@ -126,14 +155,21 @@ sub configure_test_server_for_ssl
 	close $sslconf;
 
 	# Copy all server certificates and keys, and client root cert, to the data dir
-	copy_files("ssl/server-*.crt", $pgdata);
-	copy_files("ssl/server-*.key", $pgdata);
-	chmod(0600, glob "$pgdata/server-*.key") or die $!;
-	copy_files("ssl/root+client_ca.crt", $pgdata);
-	copy_files("ssl/root_ca.crt",        $pgdata);
-	copy_files("ssl/root+client.crl",    $pgdata);
-	mkdir("$pgdata/root+client-crldir");
-	copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+	if (defined($openssl))
+	{
+		copy_files("ssl/server-*.crt", $pgdata);
+		copy_files("ssl/server-*.key", $pgdata);
+		chmod(0600, glob "$pgdata/server-*.key") or die $!;
+		copy_files("ssl/root+client_ca.crt", $pgdata);
+		copy_files("ssl/root_ca.crt",        $pgdata);
+		copy_files("ssl/root+client.crl",    $pgdata);
+		mkdir("$pgdata/root+client-crldir");
+		copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+	}
+	elsif (defined($nss))
+	{
+		RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+	}
 
 	# Stop and restart server to load new listen_addresses.
 	$node->restart;
@@ -141,20 +177,36 @@ sub configure_test_server_for_ssl
 	# Change pg_hba after restart because hostssl requires ssl=on
 	configure_hba_for_ssl($node, $servercidr, $authmethod);
 
+	# Finally, perform backend specific configuration
+	$backend->init();
+
 	return;
 }
 
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+	return $backend->get_library();
+}
+
+sub cleanup
+{
+	$backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
 {
 	my $node     = $_[0];
 	my $certfile = $_[1];
 	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || '';
+	my $pwcmd    = $_[4] || '';
 	my $crlfile  = "root+client.crl";
 	my $crldir;
 	my $pgdata = $node->data_dir;
 
+	$keyfile = $certfile if $keyfile eq '';
+
 	# defaults to use crl file
 	if (defined $_[3] || defined $_[4])
 	{
@@ -164,13 +216,23 @@ sub switch_server_cert
 
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	print $sslconf "ssl=on\n";
-	print $sslconf "ssl_ca_file='$cafile.crt'\n";
-	print $sslconf "ssl_cert_file='$certfile.crt'\n";
-	print $sslconf "ssl_key_file='$certfile.key'\n";
+	print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
 	print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
 	print $sslconf "ssl_crl_dir='$crldir'\n"   if defined $crldir;
+	print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+	  unless $pwcmd eq '';
 	close $sslconf;
+	return;
+}
 
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+	my $node = $_[0];
+	set_server_cert(@_);
 	$node->restart;
 	return;
 }
-- 
2.24.3 (Apple Git-128)

