From 2ac09d08ed78fe2ff84491fe30313b727ceb24b7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v33 3/9] nss: Add NSS specific tests

This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.

The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
 src/test/ssl/Makefile                 | 245 ++++++++++++++++++
 src/test/ssl/t/001_ssltests.pl        | 346 +++++++++++++++++---------
 src/test/ssl/t/002_scram.pl           |  31 ++-
 src/test/ssl/t/SSL/Backend/NSS.pm     |  61 +++++
 src/test/ssl/t/SSL/Backend/OpenSSL.pm |  19 +-
 src/test/ssl/t/SSL/Server.pm          |  48 +---
 6 files changed, 579 insertions(+), 171 deletions(-)
 create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm

diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index a9eca9e049..0d6d237e7c 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
 SSLDIRS := ssl/client-crldir ssl/server-crldir \
 	ssl/root+client-crldir ssl/root+server-crldir
 
+# 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 \
+	ssl/nss/root+server_ca.crt__root+server.crldir.db \
+	ssl/nss/native_ca-root.db \
+	ssl/nss/native_server-root.db \
+	ssl/nss/native_client-root.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.
 #
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
 #
 sslfiles: $(SSLFILES) $(SSLDIRS)
 
+# 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:
@@ -67,6 +101,35 @@ 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
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+	$(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
+	for c in $(shell ls ssl/root+server-crldir) ; do \
+		echo $${c} ; \
+		openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+		crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+	done
+
 # 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
@@ -80,6 +143,86 @@ 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"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_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-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+	for c in $(shell ls ssl/root+client-crldir) ; do \
+		echo $${c} ; \
+		openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+		crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+	done
+
+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'
@@ -91,6 +234,7 @@ 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 with multi-parth DN, signed by the client CA:
 ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
 	openssl req -new -key ssl/client-dn.key -out ssl/client-dn.csr -config client-dn.config
@@ -98,6 +242,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
 	openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
 	rm ssl/client-dn.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
@@ -105,6 +270,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
@@ -137,19 +310,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
@@ -175,14 +369,65 @@ ssl/client-crldir: ssl/client.crl
 	mkdir ssl/client-crldir
 	cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
 
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+	$(MKDIR_P) ssl/nss/native_ca-$*.db
+	certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+	echo y > nss_ca_params.txt
+	echo 10 >> nss_ca_params.txt
+	echo y >> nss_ca_params.txt
+	cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+	-s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+	-x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+	--keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+	-z Makefile -Z SHA256
+	rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+	certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+	$(MKDIR_P) ssl/nss/native_server-$*.db
+	certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+	certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+		-s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+		-o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+	echo 1 > nss_server_params.txt
+	echo 9 >> nss_server_params.txt
+	cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+		-o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+		--nsCertType sslServer --certVersion 1 -Z SHA256
+	certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+	certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+	rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+	$(MKDIR_P) ssl/nss/native_client-$*.db
+	certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+	certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+		-o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+	certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+		-m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+		--certVersion 1 -Z SHA256
+	certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+	certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
 .PHONY: sslfiles-clean
 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
 	rm -rf $(SSLDIRS)
 
+.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 69f4bbc61b..305758b186 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
 
 use SSL::Server;
 
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+	$openssl = 1;
+	plan tests => 107;
+}
+elsif ($ENV{with_ssl} eq 'nss')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	$nss = 1;
+	plan tests => 107;
 }
 else
 {
-	plan tests => 103;
+	plan skip_all => 'SSL not supported by this build';
 }
 
 #### Some configuration
@@ -51,19 +60,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 
 note "testing password-protected keys";
 
-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);
+# 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);
+
+	switch_server_cert($node,
+		certfile => 'server-cn-only',
+		cafile => 'root+client_ca',
+		keyfile => 'server-password',
+		nssdatabase => 'server-cn-only.crt__server-password.key.db',
+		passphrase_cmd => 'echo wrongpassword',
+		restart => 'no' );
+
+	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);
+
+	switch_server_cert($node,
+		certfile => 'server-cn-only',
+		cafile => 'root+client_ca',
+		keyfile => 'server-password',
+		nssdatabase => 'server-cn-only.crt__server-password.key.db',
+		passphrase_cmd => 'echo secret1',
+		restart => 'no');
+
+	command_ok(
+		[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+		'restart succeeds with password-protected key file');
+	$node->_update_pid(1);
+}
 
-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);
+SKIP:
+{
+	skip "Certificate passphrases are checked on connection in NSS", 3
+	  if ($openssl);
+
+	switch_server_cert($node,
+		certfile => 'server-cn-only',
+		cafile => 'root+client_ca',
+		keyfile => 'server-password',
+		nssdatabase => 'server-cn-only.crt__server-password.key.db',
+		passphrase_cmd => 'echo wrongpassword');
+
+	$node->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");
+
+	switch_server_cert($node,
+		certfile => 'server-cn-only',
+		cafile => 'root+client_ca',
+		keyfile => 'server-password',
+		nssdatabase => 'server-cn-only.crt__server-password.key.db',
+		passphrase_cmd => 'echo secret1');
+
+	$node->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.
@@ -91,7 +149,7 @@ command_ok(
 
 note "running client tests";
 
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -109,88 +167,104 @@ $node->connect_ok(
 	"connect without server root cert sslmode=require");
 $node->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 has been marked as not trusted by the user/,
 	"connect without server root cert sslmode=verify-ca");
 $node->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 has been marked as not trusted by the user/,
 	"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.)
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
 	qr/SSL error/,
 	"connect with wrong server root cert sslmode=require");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
 	qr/SSL error/,
 	"connect with wrong server root cert sslmode=verify-ca");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
 	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.
-$node->connect_fails(
-	"$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
-	qr/SSL error/,
-	"connect with server CA cert, without root CA");
+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.
+	$node->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.
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=require");
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=verify-ca");
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=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.
-$node->connect_ok(
-	"$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
-	"cert root file that contains two certificates, order 1");
-$node->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.
+	$node->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?
+	$node->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
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
 	"sslcrl option with invalid file name");
 
-# A CRL belonging to a different CA is not accepted, fails
-$node->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", 4 if ($nss);
+	# A CRL belonging to a different CA is not accepted, fails
+	$node->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");
 
-# The same for CRL directory
-$node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
-	qr/SSL error/,
-	"directory CRL belonging to a different CA");
+	# The same for CRL directory
+	$node->connect_fails(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+		qr/SSL error/,
+		"directory CRL belonging to a different CA");
+}
 
 # With the correct CRL, succeeds (this cert is not revoked)
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
 	"CRL with a non-revoked cert");
 
 # The same for CRL directory
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
 	"directory 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 ssldatabase=ssl/nss/root+server_ca.crt.db";
 
 $node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
 	"mismatch between host name and server certificate sslmode=require");
@@ -199,14 +273,14 @@ $node->connect_ok(
 	"mismatch between host name and server certificate sslmode=verify-ca");
 $node->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');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
 
 $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 ssldatabase=ssl/nss/root+server_ca.crt.db";
 
 $node->connect_ok(
 	"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -219,19 +293,19 @@ $node->connect_ok("$common_connstr host=foo.wildcard.pg-ssltest.test",
 
 $node->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");
 $node->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
 # slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
 
 $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 ssldatabase=ssl/nss/root+server_ca.crt.db";
 
 $node->connect_ok(
 	"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -239,20 +313,20 @@ $node->connect_ok(
 
 $node->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");
 $node->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"
 );
 
 # Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
 
 $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 ssldatabase=ssl/nss/root+server_ca.crt.db";
 
 $node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
 	"certificate with both a CN and SANs 1");
@@ -260,40 +334,39 @@ $node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
 	"certificate with both a CN and SANs 2");
 $node->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');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
 $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 ssldatabase=ssl/nss/root+server_ca.crt.db";
 
 $node->connect_ok(
 	"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
 	"server certificate without CN or SANs sslmode=verify-ca");
 $node->connect_fails(
-	$common_connstr . " "
-	  . "sslmode=verify-full host=common-name.pg-ssltest.test",
-	qr/could not get server's host name from server certificate/,
+	"$common_connstr sslmode=verify-full host=common-name.pg-ssltest.test",
+	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
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 # Without the CRL, succeeds. With it, fails.
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
 	"connects without client-side CRL");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl ssldatabase=ssl/nss/root+server_ca.crt__server.crl.db",
 	qr/SSL error/,
 	"does not connect with client-side CRL file");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
 	qr/SSL error/,
 	"does not connect with client-side CRL directory");
 
@@ -313,29 +386,46 @@ command_like(
 
 # Test min/max SSL protocol versions.
 $node->connect_ok(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
 	"connection success with correct range of TLS protocol versions");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
 	qr/invalid SSL protocol version range/,
 	"connection failure with incorrect range of TLS protocol versions");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
 	qr/invalid ssl_min_protocol_version value/,
 	"connection failure with an incorrect SSL protocol minimum bound");
 $node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
 	qr/invalid ssl_max_protocol_version value/,
 	"connection failure with an incorrect SSL protocol maximum bound");
 
+# tests of NSS generated certificates/keys
+SKIP:
+{
+	skip "NSS specific tests",            1 if ($openssl);
+
+	switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+	$common_connstr =
+	  "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+	$node->connect_ok(
+		"$common_connstr sslmode=require user=ssltestuser",
+		"NSS generated certificates"
+	);
+}
+
 ### Server-side tests.
 ###
 ### Test certificate authorization.
 
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
 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 ssldatabase=ssl/nss/client.crt__client.key.db";
 
 # no client cert
 $node->connect_fails(
@@ -349,54 +439,67 @@ $node->connect_ok(
 	"certificate authorization succeeds with correct client cert in PEM format"
 );
 
-# correct client cert in unencrypted DER
-$node->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
+	$node->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
 $node->connect_ok(
-	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=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
-$node->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
+	$node->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
 $node->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!,
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=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"
 );
 
+SKIP:
+{
+	skip "DN mapping not implemented in NSS",            3 if ($nss);
 
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
-	"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
-	"certificate authorization succeeds with DN mapping");
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
+	# correct client cert using whole DN
+	my $dn_connstr = "$common_connstr dbname=certdb_dn";
 
-$node->connect_ok(
-	"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
-	"certificate authorization succeeds with DN regex mapping");
+	$node->connect_ok(
+		"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+		"certificate authorization succeeds with DN mapping");
 
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
+	# same thing but with a regex
+	$dn_connstr = "$common_connstr dbname=certdb_dn_re";
 
-$node->connect_ok(
-	"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
-	"certificate authorization succeeds with CN mapping");
+	$node->connect_ok(
+		"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+		"certificate authorization succeeds with DN regex mapping");
 
+	# same thing but using explicit CN
+	$dn_connstr = "$common_connstr dbname=certdb_cn";
 
+	$node->connect_ok(
+		"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+		"certificate authorization succeeds with CN mapping");
+}
 
 TODO:
 {
@@ -432,18 +535,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 ssldatabase=ssl/nss/client.crt__client.key.db",
 		'-c',
 		"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
 	],
 	qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
-				^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+				^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?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);
 
 	$node->connect_fails(
 		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -454,10 +558,13 @@ SKIP:
 # client cert belonging to another user
 $node->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 ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
 # revoked client cert
 $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
@@ -468,7 +575,7 @@ $node->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 ssldatabase=ssl/nss/client.crt__client.key.db";
 
 $node->connect_ok(
 	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -489,23 +596,24 @@ $node->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');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
 $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 ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
 
 $node->connect_ok(
 	"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
 	"intermediate client certificate is provided by client");
 $node->connect_fails(
-	$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
-	qr/SSL error/, "intermediate client certificate is missing");
+	"$common_connstr sslmode=require sslcert=ssl/client.crt",
+	qr/connection requires a valid client certificate|SSL error/,
+	"intermediate client certificate is missing");
 
 # test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
 
 # revoked client cert
 $node->connect_fails(
-	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
 	qr/SSL error/,
 	"certificate authorization fails with revoked client cert with server-side CRL directory");
 
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a2993b8f22..7b216452cd 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
 
 use SSL::Server;
 
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+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;
+	$supports_tls_server_end_point = 1;
+	plan tests => 9;
+}
+else
 {
 	plan skip_all => 'SSL not supported by this build';
 }
@@ -23,12 +42,6 @@ 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;
 
@@ -47,7 +60,7 @@ $node->start;
 # Configure server for SSL connections, with password handling.
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
 $ENV{PGPASSWORD} = "pass";
 $common_connstr =
   "dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -92,7 +105,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
 copy("ssl/client.key", $client_tmp_key);
 chmod 0600, $client_tmp_key;
 $node->connect_fails(
-	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
 	qr/channel binding required, but server authenticated client without channel binding/,
 	"Cert authentication and channel_binding=require");
 
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..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+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 $params = $_[1];
+
+	$params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+	my $sslconf =
+	    "ssl_ca_file='$params->{cafile}.crt'\n"
+	  . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+	  . "ssl_crl_file=''\n"
+	  . "ssl_database='nss/$params->{nssdatabase}'\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
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
 # 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 $self   = $_[0];
+	my $params = $_[1];
+
+	$params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+	$params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+	$params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
 
 	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";
+	    "ssl_ca_file='$params->{cafile}.crt'\n"
+	  . "ssl_cert_file='$params->{certfile}.crt'\n"
+	  . "ssl_key_file='$params->{keyfile}.key'\n"
+	  . "ssl_crl_file='$params->{crlfile}'\n";
+	$sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
 
 	return $sslconf;
 }
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 19ccdd95f9..348e48af25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
 use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
-  set_server_cert
   switch_server_cert
 );
 
@@ -167,46 +166,25 @@ sub cleanup
 	$backend->cleanup();
 }
 
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect.  Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_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])
-	{
-		$crlfile = $_[3];
-		$crldir = $_[4];
-	}
+	my $node   = shift;
+	my %params = @_;
+	my $pgdata = $node->data_dir;
 
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	print $sslconf "ssl=on\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 '';
+	print $sslconf $backend->set_server_cert(\%params);
+	print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+	  if defined $params{passphrase_cmd};
 	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(@_);
+	return if (defined($params{restart}) && $params{restart} eq 'no');
+
 	$node->restart;
 	return;
 }
-- 
2.21.1 (Apple Git-122.3)

