From 78c1cf721cce7bc2291d061e4af6dc9eccf58fd9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v20 2/5] NSS Testharness updates

TODO:
	* Generate certificate/keys with NSS tooling for NSS specific test
	* Add test for certificate with embedded nulls
	* Fix SCRAM tests when channel binding works
---
 src/test/Makefile                             |   2 +-
 src/test/modules/Makefile                     |   2 +-
 .../modules/ssl_passphrase_callback/Makefile  |   2 +-
 .../ssl_passphrase_callback/t/001_testfunc.pl |   4 +-
 src/test/ssl/Makefile                         | 177 +++++++++-
 src/test/ssl/t/001_ssltests.pl                | 328 ++++++++++--------
 src/test/ssl/t/002_scram.pl                   |  97 ++++--
 src/test/ssl/t/SSL/Backend/NSS.pm             |  64 ++++
 src/test/ssl/t/SSL/Backend/OpenSSL.pm         | 103 ++++++
 .../ssl/t/{SSLServer.pm => SSL/Server.pm}     |  80 ++++-
 10 files changed, 664 insertions(+), 195 deletions(-)
 create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
 create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
 rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)

diff --git a/src/test/Makefile b/src/test/Makefile
index 14cde4f5ba..1b6876ca18 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
 SUBDIRS += ldap
 endif
 endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
 ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
 SUBDIRS += ssl
 endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
 		  unsafe_tests \
 		  worker_spi
 
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
 SUBDIRS += ssl_passphrase_callback
 else
 ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
 # ssl_passphrase_callback Makefile
 
-export with_openssl
+export with_ssl
 
 MODULE_big = ssl_passphrase_func
 OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
 use Test::More;
 use PostgresNode;
 
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
 {
-	plan skip_all => 'SSL not supported by this build';
+	plan skip_all => 'OpenSSL not supported by this build';
 }
 
 my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..21a654b8a9 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-export with_openssl
+export with_ssl
 
 CERTIFICATES := server_ca server-cn-and-alt-names \
 	server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
 	ssl/client+client_ca.crt ssl/client-der.key \
 	ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
 
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+	ssl/nss/server_ca.crt.db \
+	ssl/nss/root+server_ca.crt.db \
+	ssl/nss/root+client_ca.crt.db \
+	ssl/nss/client.crt__client.key.db \
+	ssl/nss/client-revoked.crt__client-revoked.key.db \
+	ssl/nss/server-cn-only.crt__server-password.key.db \
+	ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+	ssl/nss/root.crl \
+	ssl/nss/server.crl \
+	ssl/nss/client.crl \
+	ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+	ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+	ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+	ssl/nss/server-no-names.crt__server-no-names.key.db \
+	ssl/nss/server-revoked.crt__server-revoked.key.db \
+	ssl/nss/root+client.crl \
+	ssl/nss/client+client_ca.crt__client.key.db \
+	ssl/nss/client.crt__client-encrypted-pem.key.db \
+	ssl/nss/root+server_ca.crt__server.crl.db \
+	ssl/nss/root+server_ca.crt__root+server.crl.db
+
 # This target re-generates all the key and certificate files. Usually we just
 # use the ones that are committed to the tree without rebuilding them.
 #
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
 #
 sslfiles: $(SSLFILES)
 
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
 # OpenSSL requires a directory to put all generated certificates in. We don't
 # use this for anything, but we need a location.
 ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
 	rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
 	echo "01" > ssl/$*_ca.srl
 
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	crlutil -I -i ssl/nss/root.crl -d $@ -B
+	crlutil -I -i ssl/nss/server.crl -d $@ -B
+
 # Server certificates, signed by server CA:
 ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
 	openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
 	openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt  -extensions v3_req -extfile server-cn-only.config
 	rm ssl/server-ss.csr
 
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+	$(MKDIR_P) $@
+	echo "secret1" > password.txt
+	certutil -d "sql:$@" -N -f password.txt
+	certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+	openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+	pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+	pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+	pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+	pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+	pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+	pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+	pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
 # Password-protected version of server-cn-only.key
 ssl/server-password.key: ssl/server-cn-only.key
 	openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
 	openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
 	rm ssl/client.csr ssl/temp.crt
 
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+	pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+	$(MKDIR_P) $@
+	echo 'dUmmyP^#+' > $@.pass
+	certutil -d "sql:$@" -N -f $@.pass
+	certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+	pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
 # Another client certificate, signed by the client CA. This one is revoked.
 ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
 	openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
 	openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
 	rm ssl/client-revoked.csr ssl/temp.crt
 
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+	pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
 # Convert the key to DER, to test our behaviour there too
 ssl/client-der.key: ssl/client.key
 	openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
 ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
 	cat $^ > $@
 
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+	pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
 #### CRLs
 
 ssl/client.crl: ssl/client-revoked.crt
 	openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
 	openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
 
+ssl/nss/client.crl: ssl/client.crl
+	openssl crl -in $^ -outform der -out $@
+
 ssl/server.crl: ssl/server-revoked.crt
 	openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
 	openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
 
+ssl/nss/server.crl: ssl/server.crl
+	openssl crl -in $^ -outform der -out $@
+
 ssl/root.crl: ssl/root_ca.crt
 	openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
 
+ssl/nss/root.crl: ssl/root.crl
+	openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+	openssl crl -in $^ -outform der -out $@
+
 # If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
 # chain, even if some of them are empty.
 ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
 sslfiles-clean:
 	rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
 
+.PHONY: nssfiles-clean
+nssfiles-clean:
+	rm -rf ssl/nss
+
 clean distclean maintainer-clean:
 	rm -rf tmp_check
 	rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+	rm -rf ssl/nss
 
 # Doesn't depend on $(SSLFILES) because we don't rebuild them by default
 check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..5f75d4937d 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
 use TestLib;
 use Test::More;
 
-use File::Copy;
-
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
 
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
 {
+	$openssl = 1;
 	plan tests => 93;
 }
+elsif ($ENV{with_ssl} eq 'nss')
+{
+	$nss = 1;
+	plan tests => 98;
+}
 else
 {
 	plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,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 ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
-	"client",     "client-revoked",
-	"client-der", "client-encrypted-pem",
-	"client-encrypted-der");
-foreach my $key (@keys)
-{
-	copy("ssl/${key}.key", "ssl/${key}_tmp.key")
-	  or die
-	  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
-	chmod 0600, "ssl/${key}_tmp.key"
-	  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable.  We can't
-# necessarily rely on the file in the source tree having those
-# permissions.  Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
 #### Set up the server.
 
 note "setting up data directory";
@@ -72,36 +53,65 @@ $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;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+	skip "Certificate passphrases aren't checked on server restart in NSS", 2
+	  if ($nss);
+
+	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);
+
+	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');
+	$node->_update_pid(1);
+}
 
-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);
+SKIP:
+{
+	skip "Certificate passphrases are checked on connection in NSS", 5
+	  if ($openssl);
 
-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 wrongpassword');
+	command_ok(
+		[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+		'restart fails with password-protected key file with wrong password');
+	$node->_update_pid(1);
 
-command_ok(
-	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
-	'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+	test_connect_fails(
+		"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+		"sslrootcert=invalid sslmode=require",
+		qr/\QSSL error\E/,
+		"connect to server with incorrect key password configured");
+
+	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 fails with password-protected key file with wrong password');
+	$node->_update_pid(1);
+
+	test_connect_ok(
+		"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+		"sslrootcert=invalid sslmode=require",
+		"connect to server with correct key password configured");
+}
 
 # Test compatibility of SSL protocols.
 # TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"sslrootcert=invalid sslmode=verify-ca",
-	qr/root certificate file "invalid" does not exist/,
+	qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
 	"connect without server root cert sslmode=verify-ca");
 test_connect_fails(
 	$common_connstr,
 	"sslrootcert=invalid sslmode=verify-full",
-	qr/root certificate file "invalid" does not exist/,
+	qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
 	"connect without server root cert sslmode=verify-full");
 
 # Try with wrong root cert, should fail. (We're using the client CA as the
 # root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=require",
-	qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
-	qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=verify-full",
-	qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
-	qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+	$common_connstr,
+	"sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=require");
+test_connect_fails(
+	$common_connstr,
+	"sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+	$common_connstr,
+	"sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+	# NSS supports partial chain validation, so this test doesn't work there.
+	# This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+	# we don't allow.
+	skip "NSS support partial chain validation", 2 if ($nss);
+	# Try with just the server CA's cert. This fails because the root file
+	# must contain the whole chain up to the root CA.
+	test_connect_fails($common_connstr,
+		"sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+		qr/SSL error/, "connect with server CA cert, without root CA");
+}
 
 # And finally, with the correct root cert.
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=require");
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=verify-ca");
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=verify-full");
 
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
-	"cert root file that contains two certificates, order 1");
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
-	"cert root file that contains two certificates, order 2");
+SKIP:
+{
+	skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
 
+	# Test with cert root file that contains two certificates. The client should
+	# be able to pick the right one, regardless of the order in the file.
+	test_connect_ok(
+		$common_connstr,
+		"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+		"cert root file that contains two certificates, order 1");
+
+	# How about import the both-file into a database?
+	test_connect_ok(
+		$common_connstr,
+		"sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+		"cert root file that contains two certificates, order 2");
+}
 # CRL tests
 
 # Invalid CRL filename is the same as no CRL, succeeds
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
 	"sslcrl option with invalid file name");
 
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
-	qr/SSL error/,
-	"CRL belonging to a different CA");
+SKIP:
+{
+	skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+	# A CRL belonging to a different CA is not accepted, fails
+	test_connect_fails(
+		$common_connstr,
+		"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+		qr/SSL error/,
+		"CRL belonging to a different CA");
+}
 
 # With the correct CRL, succeeds (this cert is not revoked)
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
 	"CRL with a non-revoked cert");
 
 # Check that connecting with verify-full fails, when the hostname doesn't
 # match the hostname in the server's certificate.
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"sslmode=verify-full host=wronghost.test",
-	qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+	qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
 	"mismatch between host name and server certificate sslmode=verify-full");
 
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"host=wronghost.alt-name.pg-ssltest.test",
-	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
 	"host name not matching with X.509 Subject Alternative Names");
 test_connect_fails(
 	$common_connstr,
 	"host=deep.subdomain.wildcard.pg-ssltest.test",
-	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
 	"host name not matching with X.509 Subject Alternative Names wildcard");
 
 # Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
 switch_server_cert($node, 'server-single-alt-name');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"host=wronghost.alt-name.pg-ssltest.test",
-	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
 	"host name not matching with a single X.509 Subject Alternative Name");
 test_connect_fails(
 	$common_connstr,
 	"host=deep.subdomain.wildcard.pg-ssltest.test",
-	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
 	"host name not matching with a single X.509 Subject Alternative Name wildcard"
 );
 
@@ -299,7 +332,7 @@ test_connect_fails(
 switch_server_cert($node, 'server-cn-and-alt-names');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"host=common-name.pg-ssltest.test",
-	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
 	"certificate with both a CN and SANs ignores CN");
 
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"sslmode=verify-full host=common-name.pg-ssltest.test",
-	qr/could not get server's host name from server certificate/,
+	qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
 	"server certificate without CN or SANs sslmode=verify-full");
 
 # Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
 # Without the CRL, succeeds. With it, fails.
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
 	"connects without client-side CRL");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
 	qr/SSL error/,
 	"does not connect with client-side CRL");
 
@@ -365,21 +398,21 @@ command_like(
 # Test min/max SSL protocol versions.
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
 	"connection success with correct range of TLS protocol versions");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
 	qr/invalid SSL protocol version range/,
 	"connection failure with incorrect range of TLS protocol versions");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
 	qr/invalid ssl_min_protocol_version value/,
 	"connection failure with an incorrect SSL protocol minimum bound");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
 	qr/invalid ssl_max_protocol_version value/,
 	"connection failure with an incorrect SSL protocol maximum bound");
 
@@ -390,7 +423,7 @@ test_connect_fails(
 note "running server tests";
 
 $common_connstr =
-  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
 
 # no client cert
 test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
 	"certificate authorization succeeds with correct client cert in PEM format"
 );
 
-# correct client cert in unencrypted DER
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
-	"certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+	skip "NSS database not implemented in the Makefile", 1 if ($nss);
+	# correct client cert in unencrypted DER
+	test_connect_ok(
+		$common_connstr,
+		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+		"certificate authorization succeeds with correct client cert in DER format"
+	);
+}
 
 # correct client cert in encrypted PEM
 test_connect_ok(
 	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
 	"certificate authorization succeeds with correct client cert in encrypted PEM format"
 );
 
-# correct client cert in encrypted DER
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
-	"certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+	skip "NSS database not implemented in the Makefile", 1 if ($nss);
+	# correct client cert in encrypted DER
+	test_connect_ok(
+		$common_connstr,
+		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+		"certificate authorization succeeds with correct client cert in encrypted DER format"
+	);
+}
 
 # correct client cert in encrypted PEM with wrong password
 test_connect_fails(
 	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
-	qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+	qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
 	"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
 );
 
@@ -471,18 +515,19 @@ command_like(
 		'-P',
 		'null=_null_',
 		'-d',
-		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
 		'-c',
 		"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
 	],
 	qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
-				^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+				^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
 	'pg_stat_ssl with client certificate');
 
 # client key with wrong permissions
 SKIP:
 {
 	skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+	skip "Key not on filesystem with NSS",            2 if ($nss);
 
 	test_connect_fails(
 		$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
 test_connect_fails(
 	$common_connstr,
 	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
-	qr/certificate authentication failed for user "anotheruser"/,
+	qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
 	"certificate authorization fails with client cert belonging to another user"
 );
 
+$common_connstr =
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
 # revoked client cert
 test_connect_fails(
 	$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
 # works, iff username matches Common Name
 # fails, iff username doesn't match Common Name.
 $common_connstr =
-  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
 # intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
 switch_server_cert($node, 'server-cn-only', 'root_ca');
 $common_connstr =
-  "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+  "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
 
-test_connect_ok(
+TODO:
+{
+	local $TODO = "WIP failure cause currently unknown";
+	test_connect_ok(
+		$common_connstr,
+		"sslmode=require sslcert=ssl/client+client_ca.crt",
+		"intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
 	$common_connstr,
-	"sslmode=require sslcert=ssl/client+client_ca.crt",
-	"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
-	qr/SSL error/, "intermediate client certificate is missing");
+	"sslmode=require sslcert=ssl/client.crt",
+	qr/connection requires a valid client certificate|SSL error/,
+	"intermediate client certificate is missing");
 
 # clean up
-foreach my $key (@keys)
-{
-	unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..cf5714e544 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,24 +11,39 @@ use File::Copy;
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
-if ($ENV{with_openssl} ne 'yes')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'no')
 {
 	plan skip_all => 'SSL not supported by this build';
 }
+else
+{
+	if ($ENV{with_ssl} eq 'openssl')
+	{
+		$openssl = 1;
+		# Determine whether build supports tls-server-end-point.
+		$supports_tls_server_end_point =
+			check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+		plan tests => $supports_tls_server_end_point ? 9 : 10;
+	}
+	elsif ($ENV{with_ssl} eq 'nss')
+	{
+		$nss = 1;
+		plan tests => 8;
+	}
+}
 
 # This is the hostname used to connect to the server.
 my $SERVERHOSTADDR = '127.0.0.1';
 # This is the pattern to use in pg_hba.conf to match incoming connections.
 my $SERVERHOSTCIDR = '127.0.0.1/32';
 
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
-  check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
 
@@ -66,20 +81,25 @@ test_connect_ok(
 	$common_connstr,
 	"user=ssltestuser channel_binding=disable",
 	"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
-	test_connect_ok(
-		$common_connstr,
-		"user=ssltestuser channel_binding=require",
-		"SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
 {
-	test_connect_fails(
-		$common_connstr,
-		"user=ssltestuser channel_binding=require",
-		qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
-		"SCRAM with SSL and channel_binding=require");
+	skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+	if ($supports_tls_server_end_point)
+	{
+		test_connect_ok(
+			$common_connstr,
+			"user=ssltestuser channel_binding=require",
+			"SCRAM with SSL and channel_binding=require");
+	}
+	else
+	{
+		test_connect_fails(
+			$common_connstr,
+			"user=ssltestuser channel_binding=require",
+			qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+			"SCRAM with SSL and channel_binding=require");
+	}
 }
 
 # Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +109,21 @@ test_connect_fails(
 	qr/channel binding required but not supported by server's authentication request/,
 	"MD5 with SSL and channel_binding=require");
 
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed.  Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
-	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
-	"dbname=certdb user=ssltestuser channel_binding=require",
-	qr/channel binding required, but server authenticated client without channel binding/,
-	"Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+	skip "NSS support not implemented yet", 1 if ($nss);
+	# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+	# because channel binding is not performed.  Note that ssl/client.key may
+	# be used in a different test, so the name of this temporary client key
+	# is chosen here to be unique.
+	my $client_tmp_key = "ssl/client_scram_tmp.key";
+	copy("ssl/client.key", $client_tmp_key);
+	chmod 0600, $client_tmp_key;
+	test_connect_fails(
+		"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+		"dbname=certdb user=ssltestuser channel_binding=require",
+		qr/channel binding required, but server authenticated client without channel binding/,
+		"Cert authentication and channel_binding=require");
+	# clean up
+	unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA       = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+	my ($class) = @_;
+
+	my $self = { _library => 'NSS' };
+
+	bless $self, $class;
+
+	return $self;
+}
+
+sub get_new_nss_backend
+{
+	my $class = 'SSL::Backend::NSS';
+
+	return $class->new();
+}
+
+sub init
+{
+	# Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+	my ($self) = @_;
+
+	return $self->{_library};
+}
+
+sub set_server_cert
+{
+	my $self     = $_[0];
+	my $certfile = $_[1];
+	my $cafile   = $_[2];
+	my $keyfile  = $_[3];
+
+	my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+	my $cert_database = $cert_nickname . '.db';
+
+	my $sslconf =
+	    "ssl_ca_file='$cafile.crt'\n"
+	  . "ssl_cert_file='ssl/$certfile.crt'\n"
+	  . "ssl_crl_file=''\n"
+	  . "ssl_database='nss/$cert_database'\n";
+
+	return $sslconf;
+}
+
+sub cleanup
+{
+	# Something?
+}
+
+1;
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..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+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);
+
+our (@keys);
+
+INIT
+{
+	@keys = (
+		"client",     "client-revoked",
+		"client-der", "client-encrypted-pem",
+		"client-encrypted-der");
+}
+
+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 ssl/client.key to ssl/client_tmp.key etc for the rest
+	# of the tests.
+	foreach my $key (@keys)
+	{
+		copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+		  or die
+		  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+		chmod 0600, "ssl/${key}_tmp.key"
+		  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+	}
+
+	# Also make a copy of that explicitly world-readable.  We can't
+	# necessarily rely on the file in the source tree having those
+	# permissions. Add it to @keys to include it in the final clean
+	# up phase.
+	copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+	  or die
+	  "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+	chmod 0644, "ssl/client_wrongperms_tmp.key"
+	  or die
+	  "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+	push @keys, 'client_wrongperms';
+}
+
+# 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
+{
+	foreach my $key (@keys)
+	{
+		unlink("ssl/${key}_tmp.key");
+	}
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
 # explicitly because an invalid sslcert or sslrootcert, respectively,
 # causes those to be ignored.)
 
-package SSLServer;
+package SSL::Server;
 
 use strict;
 use warnings;
 use PostgresNode;
+use RecursiveCopy;
 use TestLib;
 use File::Basename;
 use File::Copy;
 use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+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
   test_connect_fails
   test_connect_ok
@@ -144,12 +164,19 @@ 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);
+	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);
+	}
+	elsif (defined($nss))
+	{
+		RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+	}
 
 	# Stop and restart server to load new listen_addresses.
 	$node->restart;
@@ -157,26 +184,51 @@ 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 $pgdata   = $node->data_dir;
 
+	$keyfile = $certfile if $keyfile eq '';
+
 	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 "ssl_crl_file='root+client.crl'\n";
+	print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+	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.21.1 (Apple Git-122.3)

