public inbox for [email protected]  
help / color / mirror / Atom feed
From: Daniel Gustafsson <[email protected]>
To: Rachel Heaton <[email protected]>
Cc: [email protected] <[email protected]>
Cc: Michael Paquier <[email protected]>
Cc: Jacob Champion <[email protected]>
Cc: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>
Subject: Re: Support for NSS as a libpq TLS backend
Date: Thu, 30 Sep 2021 14:17:29 +0200
Message-ID: <[email protected]> (raw)
In-Reply-To: <CADJcwiVzWTs_xtt421ghkxAtoJ1p472psOHe6kAjHdN=X9kY+g@mail.gmail.com>
References: <[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<CADJcwiVzWTs_xtt421ghkxAtoJ1p472psOHe6kAjHdN=X9kY+g@mail.gmail.com>

> On 28 Sep 2021, at 01:07, Rachel Heaton <[email protected]> wrote:

> 1. I get 7 warnings while running make (truncated):
> cryptohash_nss.c:101:21: warning: implicit conversion from enumeration
> type 'SECOidTag' to different enumeration type 'HASH_HashType'

Nice catch, fixed in the attached.

> 2. libpq-refs-stamp fails -- it appears an exit is being injected into
> libpq on Mac

I spent some time investigating this, and there are two cases of _exit() and
one atexit() which are coming from the threading code in libnspr (which is the
runtime lib required by libnss).

On macOS the threading code registers an atexit handler [0] in order to work
around issues with __attribute__((destructor)) [1].  The pthreads code also
defines PR_ProcessExit [2] which does what it says on the tin, calls exit and
not much more [3].  Both of these uses are only compiled when building with
pthreads, which can be disabled in autoconf but that seems broken in recent
version of NSPR.  I'm fairly sure I've built NSPR with the user pthreads in the
past, but if packagers build it like this then we need to conform to that.  The
PR_CreateProcess() [4] call further calls _exit() [5] in a number of error
paths on failing syscalls.

The libpq libnss implementation doesn't call either of these, and neither does
libnss.

I'm not entirely sure what to do here, it clearly requires an exception in the
Makefile check of sorts if we deem we can live with this.

@Jacob: how did you configure your copy of NSPR?

--
Daniel Gustafsson		https://vmware.com/

[0] https://hg.mozilla.org/projects/nspr/file/tip/pr/src/pthreads/ptthread.c#l1034
[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1399746#c99
[2] https://www-archive.mozilla.org/projects/nspr/reference/html/prinit.html#15859
[3] https://hg.mozilla.org/projects/nspr/file/tip/pr/src/pthreads/ptthread.c#l1181
[4] https://www-archive.mozilla.org/projects/nspr/reference/html/prprocess.html#24535
[5] https://hg.mozilla.org/projects/nspr/file/tip/pr/src/md/unix/uxproces.c#l268



Attachments:

  [application/octet-stream] v44-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch (102.6K, ../[email protected]/2-v44-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch)
  download | inline diff:
From 8c901a5beb0d1063f8a8f20fadb76f5467b29741 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v44 01/10] nss: Support libnss as TLS library in libpq

This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.

A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.

Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.

Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
 .../postgres_fdw/expected/postgres_fdw.out    |    2 +-
 src/backend/libpq/auth.c                      |    6 +
 src/backend/libpq/be-secure-nss.c             | 1574 +++++++++++++++++
 src/backend/libpq/be-secure.c                 |    1 +
 src/backend/utils/misc/guc.c                  |   18 +-
 src/common/cipher_nss.c                       |  192 ++
 src/common/protocol_nss.c                     |   59 +
 src/include/common/nss.h                      |   52 +
 src/include/libpq/libpq-be.h                  |   15 +-
 src/include/libpq/libpq.h                     |    1 +
 src/include/pg_config_manual.h                |    2 +-
 src/interfaces/libpq/fe-connect.c             |    4 +
 src/interfaces/libpq/fe-secure-nss.c          | 1200 +++++++++++++
 src/interfaces/libpq/fe-secure.c              |   21 +
 src/interfaces/libpq/libpq-fe.h               |   11 +
 src/interfaces/libpq/libpq-int.h              |   29 +-
 16 files changed, 3180 insertions(+), 7 deletions(-)
 create mode 100644 src/backend/libpq/be-secure-nss.c
 create mode 100644 src/common/cipher_nss.c
 create mode 100644 src/common/protocol_nss.c
 create mode 100644 src/include/common/nss.h
 create mode 100644 src/interfaces/libpq/fe-secure-nss.c

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c7b7db8065..6d63528954 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
     END;
 $d$;
 ERROR:  invalid option "password"
-HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
 CONTEXT:  SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
 PL/pgSQL function inline_code_block line 3 at EXECUTE
 -- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
 	int			status_check_usermap = STATUS_ERROR;
 	char	   *peer_username = NULL;
 
+#if defined(USE_OPENSSL)
 	Assert(port->ssl);
+#elif defined(USE_NSS)
+	Assert(port->pr_fd);
+#else
+	Assert(false);
+#endif
 
 	/* select the correct field to compare */
 	switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ *	  functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ *		"In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ *		implementation changes. In practice, this is unlikely to happen because
+ *		NSPR's implementation has been stable for years and because of NSPR's
+ *		strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+									  PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/*						 Public interface						*/
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ *			Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+	SECStatus	status;
+	SSLVersionRange supported_sslver;
+
+	status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+	if (status != SECSuccess)
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				(errmsg("unable to connect to TLS connection cache: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return -1;
+	}
+
+	if (!ssl_database || strlen(ssl_database) == 0)
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				(errmsg("no certificate database specified")));
+		return -1;
+	}
+
+	/*
+	 * We check for the desired TLS version range here, even though we cannot
+	 * set it until be_open_server such that we can be compatible with how the
+	 * OpenSSL backend reports errors for incompatible range configurations.
+	 * Set either the default supported TLS version range, or the configured
+	 * range from ssl_min_protocol_version and ssl_max_protocol version. In
+	 * case the user hasn't defined the maximum allowed version we fall back
+	 * to the highest version TLS that the library supports.
+	 */
+	if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				(errmsg("unable to get default protocol support from NSS")));
+		return -1;
+	}
+
+	/*
+	 * Set the fallback versions for the TLS protocol version range to a
+	 * combination of our minimal requirement and the library maximum. Error
+	 * messages should be kept identical to those in be-secure-openssl.c to
+	 * make translations easier.
+	 */
+	desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+	desired_sslver.max = supported_sslver.max;
+
+	if (ssl_min_protocol_version)
+	{
+		int			ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+		if (ver == -1)
+		{
+			ereport(isServerStart ? FATAL : LOG,
+					(errmsg("\"%s\" setting \"%s\" not supported by this build",
+							"ssl_min_protocol_version",
+							GetConfigOption("ssl_min_protocol_version",
+											false, false))));
+			return -1;
+		}
+
+		if (ver > 0)
+			desired_sslver.min = ver;
+	}
+
+	if (ssl_max_protocol_version)
+	{
+		int			ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+		if (ver == -1)
+		{
+			ereport(isServerStart ? FATAL : LOG,
+					(errmsg("\"%s\" setting \"%s\" not supported by this build",
+							"ssl_max_protocol_version",
+							GetConfigOption("ssl_max_protocol_version",
+											false, false))));
+			return -1;
+		}
+		if (ver > 0)
+			desired_sslver.max = ver;
+
+		if (ver < desired_sslver.min)
+		{
+			ereport(isServerStart ? FATAL : LOG,
+					(errmsg("could not set SSL protocol version range"),
+					 errdetail("\"%s\" cannot be higher than \"%s\"",
+							   "ssl_min_protocol_version",
+							   "ssl_max_protocol_version")));
+			return -1;
+		}
+	}
+
+	/*
+	 * Set the passphrase callback which will be used both to obtain the
+	 * passphrase from the user, as well as by NSS to obtain the phrase
+	 * repeatedly.
+	 */
+	ssl_is_server_start = isServerStart;
+	(*nss_tls_init_hook) (isServerStart);
+
+	return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+	SECStatus	status;
+	PRFileDesc *model;
+	PRFileDesc *layer;
+	CERTCertificate *server_cert;
+	SECKEYPrivateKey *private_key;
+	CERTSignedCrl *crl;
+	SECItem		crlname;
+	char	   *cert_database;
+	NSSInitParameters params;
+
+	/*
+	 * The NSPR documentation states that runtime initialization via PR_Init
+	 * is no longer required, as the first caller into NSPR will perform the
+	 * initialization implicitly. The documentation doesn't however clarify
+	 * from which version this is holds true, so let's perform the potentially
+	 * superfluous initialization anyways to avoid crashing on older versions
+	 * of NSPR, as there is no difference in overhead.  The NSS documentation
+	 * still states that PR_Init must be called in some way (implicitly or
+	 * explicitly).
+	 *
+	 * The below parameters are what the implicit initialization would've done
+	 * for us, and should work even for older versions where it might not be
+	 * done automatically. The last parameter, maxPTDs, is set to various
+	 * values in other codebases, but has been unused since NSPR 2.1 which was
+	 * released sometime in 1998. In current versions of NSPR all parameters
+	 * are ignored.
+	 */
+	PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+	/*
+	 * The certificate path (configdir) must contain a valid NSS database. If
+	 * the certificate path isn't a valid directory, NSS will fall back on the
+	 * system certificate database. If the certificate path is a directory but
+	 * is empty then the initialization will fail. On the client side this can
+	 * be allowed for any sslmode but the verify-xxx ones.
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+	 * we won't allow this to fail however, as we require the certificate and
+	 * key to exist.
+	 *
+	 * The original design of NSS was for a single application to use a single
+	 * copy of it, initialized with NSS_Initialize() which isn't returning any
+	 * handle with which to refer to NSS. NSS initialization and shutdown are
+	 * global for the application, so a shutdown in another NSS enabled
+	 * library would cause NSS to be stopped for libpq as well.  The fix has
+	 * been to introduce NSS_InitContext which returns a context handle to
+	 * pass to NSS_ShutdownContext.  NSS_InitContext was introduced in NSS
+	 * 3.12, but the use of it is not very well documented.
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+	 *
+	 * The InitParameters struct passed can be used to override internal
+	 * values in NSS, but the usage is not documented at all. When using
+	 * NSS_Init initializations, the values are instead set via PK11_Configure
+	 * calls so the PK11_Configure documentation can be used to glean some
+	 * details on these.
+	 *
+	 * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+	 */
+	memset(&params, '\0', sizeof(params));
+	params.length = sizeof(params);
+
+	if (!ssl_database || strlen(ssl_database) == 0)
+		ereport(FATAL,
+				(errmsg("no certificate database specified")));
+
+	cert_database = psprintf("sql:%s", ssl_database);
+	nss_context = NSS_InitContext(cert_database, "", "", "",
+								  &params,
+								  NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+	pfree(cert_database);
+
+	if (!nss_context)
+		ereport(FATAL,
+				(errmsg("unable to read certificate database \"%s\": %s",
+						ssl_database, pg_SSLerrmessage(PR_GetError()))));
+
+	/*
+	 * Import the already opened socket as we don't want to use NSPR functions
+	 * for opening the network socket due to how the PostgreSQL protocol works
+	 * with TLS connections. This function is not part of the NSPR public API,
+	 * see the comment at the top of the file for the rationale of still using
+	 * it.
+	 */
+	port->pr_fd = PR_ImportTCPSocket(port->sock);
+	if (!port->pr_fd)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to connect to socket")));
+		return -1;
+	}
+
+	/*
+	 * Most of the documentation available, and implementations of, NSS/NSPR
+	 * use the PR_NewTCPSocket() function here, which has the drawback that it
+	 * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+	 * copes with IPv6 as well.
+	 *
+	 * We use a model filedescriptor here which is a construct in NSPR/NSS in
+	 * order to create a configuration template for sockets which can then be
+	 * applied to new sockets created. This makes more sense in a server which
+	 * accepts multiple connections and want to perform the boilerplate just
+	 * once, but it does provide a nice abstraction here as well in that we
+	 * can error out early without having performed any operation on the real
+	 * socket.
+	 */
+	model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+	if (!model)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to open socket")));
+		return -1;
+	}
+
+	/*
+	 * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+	 * operation is critical as NSS SSL_* functions may return SECSuccess on
+	 * the socket even though SSL hasn't been enabled, which introduce a risk
+	 * of silent downgrades.
+	 */
+	model = SSL_ImportFD(NULL, model);
+	if (!model)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to enable TLS on socket")));
+		return -1;
+	}
+
+	/*
+	 * Configure basic settings for the connection over the SSL socket in
+	 * order to set it up as a server.
+	 */
+	if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to configure TLS connection")));
+		return -1;
+	}
+
+	if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+		SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to configure TLS connection as server")));
+		return -1;
+	}
+
+	/*
+	 * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+	 * of allowed protocols further down. Since we really don't want these to
+	 * ever be enabled, let's use belts and suspenders and explicitly turn
+	 * them off as well.
+	 */
+	SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+	SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+	/*
+	 * Enable protection against the BEAST attack in case the NSS server has
+	 * support for that. While SSLv3 is disabled, we may still allow TLSv1
+	 * which is affected. The option isn't documented as an SSL option, but as
+	 * an NSS environment variable.
+	 */
+	SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+	/*
+	 * Configure the allowed ciphers. If there are no user preferred suites,
+	 * set the domestic policy.
+	 *
+	 * Historically there were different cipher policies based on export (and
+	 * import) restrictions: Domestic, Export and France. These are since long
+	 * removed with all ciphers being enabled by default. Due to backwards
+	 * compatibility, the old API is still used even though all three policies
+	 * now do the same thing.
+	 *
+	 * If SSLCipherSuites define a policy of the user, we set that rather than
+	 * enabling all ciphers via NSS_SetDomesticPolicy.
+	 *
+	 * TODO: while this code works, the set of ciphers which can be set and
+	 * still end up with a working socket is woefully underdocumented for
+	 * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+	 * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+	 * this is helpful or not. Using the policies works, but may be too
+	 * coarsely grained.
+	 *
+	 * Another TODO: The SSL_ImplementedCiphers table returned with calling
+	 * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+	 * SSLCipherSuites according to the order of the ciphers therein could be
+	 * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+	 * cipher selection for NSS like how we do it for OpenSSL that is.
+	 */
+
+	/*
+	 * If no ciphers are specified, enable them all.
+	 */
+	if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+	{
+		status = NSS_SetDomesticPolicy();
+		if (status != SECSuccess)
+		{
+			ereport(COMMERROR,
+					(errmsg("unable to set cipher policy: %s",
+							pg_SSLerrmessage(PR_GetError()))));
+			return -1;
+		}
+	}
+	else
+	{
+		char	   *ciphers,
+				   *c;
+
+		char	   *sep = ":;, ";
+		PRUint16	ciphercode;
+		const		PRUint16 *nss_ciphers;
+		bool		found = false;
+
+		/*
+		 * If the user has specified a set of preferred cipher suites we start
+		 * by turning off all the existing suites to avoid the risk of down-
+		 * grades to a weaker cipher than expected.
+		 */
+		nss_ciphers = SSL_GetImplementedCiphers();
+		for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+			SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+		ciphers = pstrdup(SSLCipherSuites);
+
+		for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+		{
+			if (pg_find_cipher(c, &ciphercode))
+			{
+				status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+				found = true;
+				if (status != SECSuccess)
+				{
+					ereport(COMMERROR,
+							(errmsg("invalid cipher-suite specified: %s", c)));
+					return -1;
+				}
+			}
+		}
+
+		pfree(ciphers);
+
+		if (!found)
+		{
+			ereport(COMMERROR,
+					(errmsg("no cipher-suites found")));
+			return -1;
+		}
+	}
+
+	if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to set requested SSL protocol version range")));
+		return -1;
+	}
+
+	/*
+	 * Set up the custom IO layer in order to be able to provide custom call-
+	 * backs for IO operations which override the built-in behavior. For now
+	 * we need this in order to support debug builds of NSS/NSPR.
+	 */
+	layer = init_iolayer(port);
+	if (!layer)
+		return -1;
+
+	if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+	{
+		PR_Close(layer);
+		ereport(COMMERROR,
+				(errmsg("unable to push IO layer")));
+		return -1;
+	}
+
+	server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+	if (!server_cert)
+	{
+		if (dummy_ssl_passwd_cb_called)
+		{
+			ereport(COMMERROR,
+					(errmsg("unable to load certificate for \"%s\": %s",
+							ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+					 errhint("The certificate requires a password.")));
+			return -1;
+		}
+		else
+		{
+			ereport(COMMERROR,
+					(errmsg("unable to find certificate for \"%s\": %s",
+							ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+			return -1;
+		}
+	}
+
+	private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+	if (!private_key)
+	{
+		if (dummy_ssl_passwd_cb_called)
+		{
+			ereport(COMMERROR,
+					(errmsg("unable to load private key for \"%s\": %s",
+							ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+					 errhint("The private key requires a password.")));
+			return -1;
+		}
+		else
+		{
+			ereport(COMMERROR,
+					(errmsg("unable to find private key for \"%s\": %s",
+							ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+			return -1;
+		}
+	}
+
+	/*
+	 * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+	 * contain the CRL nickname for the current server certificate in the NSS
+	 * certificate database. The main difference from the OpenSSL backend is
+	 * that NSS will use the CRL regardless, but being able to make sure the
+	 * CRL is loaded seems like a good feature.
+	 */
+	if (ssl_crl_file[0])
+	{
+		SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+		crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+		if (!crl)
+		{
+			ereport(COMMERROR,
+					(errmsg("specified CRL not found in database")));
+			return -1;
+		}
+		SEC_DestroyCrl(crl);
+	}
+
+	/*
+	 * Finally we must configure the socket for being a server by setting the
+	 * certificate and key. The NULL parameter is an SSLExtraServerCertData
+	 * pointer with the final parameter being the size of the extra server
+	 * cert data structure pointed to. This is typically only used for
+	 * credential delegation.
+	 */
+	status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+	if (status != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to configure server for TLS server connections: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return -1;
+	}
+
+	ssl_loaded_verify_locations = true;
+
+	/*
+	 * At this point, we no longer have use for the certificate and private
+	 * key as they have been copied into the context by NSS. Destroy our
+	 * copies explicitly to clean out the memory as best we can.
+	 */
+	CERT_DestroyCertificate(server_cert);
+	SECKEY_DestroyPrivateKey(private_key);
+
+	/* Set up certificate authentication callback */
+	status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+	if (status != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to install authcert hook: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return -1;
+	}
+	SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+	SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+	SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+	/*
+	 * Apply the configuration from the model template onto our actual socket
+	 * to set it up as a TLS server.
+	 */
+	port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+	if (!port->pr_fd)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to configure socket for TLS server mode: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return -1;
+	}
+
+	/*
+	 * The intention with the model FD is to keep it as a template for coming
+	 * connections to amortize the cost and complexity across all client
+	 * sockets. Since we won't get another socket connected in this backend
+	 * we can however close the model immediately.
+	 */
+	PR_Close(model);
+
+	/*
+	 * Force a handshake on the next I/O request, the second parameter means
+	 * that we are a server, PR_FALSE would indicate being a client. NSPR
+	 * requires us to call SSL_ResetHandshake since we imported an already
+	 * established socket.
+	 */
+	status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+	if (status != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to initiate handshake: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return -1;
+	}
+	status = SSL_ForceHandshake(port->pr_fd);
+	if (status != SECSuccess)
+	{
+		ereport(COMMERROR,
+				(errmsg("unable to handshake: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return -1;
+	}
+
+	port->ssl_in_use = true;
+
+	/* Register our shutdown callback */
+	NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+	/* Register callback for TLS alerts */
+	SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+	return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+	ssize_t		n_read;
+	PRErrorCode err;
+
+	n_read = PR_Read(port->pr_fd, ptr, len);
+
+	if (n_read < 0)
+	{
+		err = PR_GetError();
+
+		if (err == PR_WOULD_BLOCK_ERROR)
+		{
+			*waitfor = WL_SOCKET_READABLE;
+			errno = EWOULDBLOCK;
+		}
+		else
+			errno = ECONNRESET;
+	}
+
+	return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+	ssize_t		n_write;
+	PRErrorCode err;
+	PRIntn		flags = 0;
+
+	/*
+	 * The flags parameter to PR_Send is no longer used and is, according to
+	 * the documentation, required to be zero.
+	 */
+	n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+	if (n_write < 0)
+	{
+		err = PR_GetError();
+
+		if (err == PR_WOULD_BLOCK_ERROR)
+		{
+			*waitfor = WL_SOCKET_WRITEABLE;
+			errno = EWOULDBLOCK;
+		}
+		else
+			errno = ECONNRESET;
+	}
+
+	return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+	if (!port)
+		return;
+	/*
+	 * Immediately signal to the rest of the backend that this connnection is
+	 * no longer to be considered to be using TLS encryption.
+	 */
+	port->ssl_in_use = false;
+
+	if (port->peer_cn)
+	{
+		SSL_InvalidateSession(port->pr_fd);
+		pfree(port->peer_cn);
+		port->peer_cn = NULL;
+	}
+
+	PR_Close(port->pr_fd);
+	port->pr_fd = NULL;
+
+	if (nss_context)
+	{
+		NSS_ShutdownContext(nss_context);
+		nss_context = NULL;
+	}
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+	/*
+	 * It reads a bit odd to clear a session cache when we are destroying the
+	 * context altogether, but if the session cache isn't cleared before
+	 * shutting down the context it will fail with SEC_ERROR_BUSY.
+	 */
+	SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+	SSLCipherSuiteInfo suite;
+
+	status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+		goto error;
+
+	status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+	if (status != SECSuccess)
+		goto error;
+
+	return suite.effectiveKeyBits;
+
+error:
+	ereport(WARNING,
+			(errmsg("unable to extract TLS session information: %s",
+					pg_SSLerrmessage(PR_GetError()))));
+	return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+
+	status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+	{
+		ereport(WARNING,
+				(errmsg("unable to extract TLS session information: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return NULL;
+	}
+
+	return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+	SSLCipherSuiteInfo suite;
+
+	status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+		goto error;
+
+	status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+	if (status != SECSuccess)
+		goto error;
+
+	return suite.cipherSuiteName;
+
+error:
+	ereport(WARNING,
+			(errmsg("unable to extract TLS session information: %s",
+					pg_SSLerrmessage(PR_GetError()))));
+	return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+	CERTCertificate *certificate;
+
+	certificate = SSL_PeerCertificate(port->pr_fd);
+	if (certificate)
+		strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+	else
+		ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+	CERTCertificate *certificate;
+
+	certificate = SSL_PeerCertificate(port->pr_fd);
+	if (certificate)
+		strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+	else
+		ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+	CERTCertificate *certificate;
+
+	certificate = SSL_PeerCertificate(port->pr_fd);
+	if (certificate)
+		snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+	else
+		ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+	CERTCertificate *certificate;
+	SECOidTag	signature_tag;
+	SECOidTag	digest_alg;
+	int			digest_len;
+	SECStatus	status;
+	PLArenaPool *arena = NULL;
+	SECItem		digest;
+	char	   *ret;
+	PK11Context *ctx = NULL;
+	unsigned int outlen;
+
+	*len = 0;
+	certificate = SSL_LocalCertificate(port->pr_fd);
+	if (!certificate)
+		return NULL;
+
+	signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+	if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+		elog(ERROR, "could not find digest for OID '%s'",
+			 SECOID_FindOIDTagDescription(signature_tag));
+
+	/*
+	 * The TLS server's certificate bytes need to be hashed with SHA-256 if
+	 * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+	 * (https://tools.ietf.org/html/rfc5929#section-4.1).  If something else
+	 * is used, the same hash as the signature algorithm is used.
+	 */
+	if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+	{
+		digest_alg = SEC_OID_SHA256;
+		digest_len = SHA256_LENGTH;
+	}
+
+	ctx = PK11_CreateDigestContext(digest_alg);
+	if (!ctx)
+		elog(ERROR, "out of memory");
+
+	arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+	digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+	if (!digest.data)
+		elog(ERROR, "out of memory");
+	digest.len = digest_len;
+
+	status = SECSuccess;
+	status |= PK11_DigestBegin(ctx);
+	status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+	status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+	if (status != SECSuccess)
+	{
+		PORT_FreeArena(arena, PR_TRUE);
+		PK11_DestroyContext(ctx, PR_TRUE);
+		elog(ERROR, "could not generate server certificate hash");
+	}
+
+	ret = palloc(digest.len);
+	memcpy(ret, digest.data, digest.len);
+	*len = digest_len;
+
+	PORT_FreeArena(arena, PR_TRUE);
+	PK11_DestroyContext(ctx, PR_TRUE);
+
+	return ret;
+}
+
+/* ------------------------------------------------------------ */
+/*						Internal functions						*/
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set.  The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+	/*
+	 * No user-defined callback has been configured, install the dummy call-
+	 * back since we must set something.
+	 */
+	if (!ssl_passphrase_command[0])
+		PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+	else
+	{
+		/*
+		 * There is a user-defined callback, set it unless we are in a restart
+		 * and cannot handle restarts due to an interactive callback.
+		 */
+		if (isServerStart)
+			PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+		else
+		{
+			if (ssl_passphrase_command_supports_reload)
+				PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+			else
+				PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+		}
+	}
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+	/*
+	 * NSS use a hardcoded 256 byte buffer for reading the password so set the
+	 * same limit for our callback buffer.
+	 */
+	char		buf[256];
+	int			len;
+	char	   *password = NULL;
+	char	   *prompt;
+
+	/*
+	 * Since there is no password callback in NSS when the server starts up,
+	 * it makes little sense to create an interactive callback. Thus, if this
+	 * is a retry attempt then give up immediately.
+	 */
+	if (retry)
+		return NULL;
+
+	/*
+	 * Construct the same prompt that NSS uses internally even though it is
+	 * unlikely to serve much purpose, but we must set a prompt so we might as
+	 * well do it right.
+	 */
+	prompt = psprintf("Enter Password or Pin for \"%s\":",
+					  PK11_GetTokenName(slot));
+
+	len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+	pfree(prompt);
+
+	if (!len)
+		return NULL;
+
+	/*
+	 * At least one byte with password content was returned, and NSS requires
+	 * that we return it allocated in NSS controlled memory. If we fail to
+	 * allocate then abort without passing back NULL and bubble up the error
+	 * on the PG side.
+	 */
+	password = (char *) PR_Malloc(len + 1);
+	if (!password)
+	{
+		explicit_bzero(buf, sizeof(buf));
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory")));
+	}
+	strlcpy(password, buf, sizeof(password));
+	explicit_bzero(buf, sizeof(buf));
+
+	return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+	dummy_ssl_passwd_cb_called = true;
+	return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+	Port	   *port = (Port *) arg;
+
+	port->peer_cert_valid = false;
+	return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+	switch(tag)
+	{
+		case SEC_OID_AVA_COMMON_NAME:
+			return "CN";
+		case SEC_OID_AVA_STATE_OR_PROVINCE:
+			return "ST";
+		case SEC_OID_AVA_ORGANIZATION_NAME:
+			return "O";
+		case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+			return "OU";
+		case SEC_OID_AVA_COUNTRY_NAME:
+			return "C";
+		case SEC_OID_AVA_LOCALITY:
+			return "L";
+		case SEC_OID_AVA_SURNAME:
+			return "SN";
+		case SEC_OID_AVA_DC:
+			return "DC";
+		case SEC_OID_RFC1274_MAIL:
+			return "MAIL";
+		case SEC_OID_RFC1274_UID:
+			return "UID";
+		/*
+		 * TODO: what would be the most appropriate thing to do here?
+		 */
+		default:
+			return "";
+	}
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+	CERTName		subject = cert->subject;
+	CERTRDN		  **rdn;
+	StringInfoData	dnbuf;
+	char		   *dn = NULL;
+
+	initStringInfo(&dnbuf);
+
+	for (rdn = subject.rdns; *rdn; rdn++)
+	{
+		CERTAVA   **ava;
+		bool		first = true;
+
+		for (ava = (*rdn)->avas; *ava; ava++)
+		{
+			SECItem	   *buf;
+			SECStatus	status;
+			char	   *tmpstr;
+			char	   *dststr;
+			int			tmplen;
+
+			/* See raw_subject_common_name comment for discussion */
+			buf = CERT_DecodeAVAValue(&(*ava)->value);
+			if (!buf)
+				return NULL;
+
+			tmpstr = palloc0(buf->len + 1);
+			tmplen = buf->len;
+			memcpy(tmpstr, buf->data, buf->len);
+			SECITEM_FreeItem(buf, PR_TRUE);
+			/* Check for embedded null */
+			if (strlen(tmpstr) != tmplen)
+				return NULL;
+
+			/* Make room for the worst case escaping */
+			dststr = palloc0((tmplen * 3) + 1);
+
+			/*
+			 * While the function name refers to RFC 1485, the underlying
+			 * quoting code conforms to the RFC 2253 rules.
+			 */
+			status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+			if (status != SECSuccess)
+			{
+				pfree(dststr);
+				pfree(tmpstr);
+				return NULL;
+			}
+
+			appendStringInfo(&dnbuf, "%s%s=%s",
+							 (first ? "" : ","),
+							 tag_to_name(CERT_GetAVATag(*ava)), dststr);
+			first = false;
+
+			pfree(dststr);
+			pfree(tmpstr);
+		}
+	}
+
+	if (dnbuf.len)
+	{
+		dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+		strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+	}
+
+	return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+	CERTName	subject = cert->subject;
+	CERTRDN	  **rdn;
+
+	for (rdn = subject.rdns; *rdn; rdn++)
+	{
+		CERTAVA	  **ava;
+
+		for (ava = (*rdn)->avas; *ava; ava++)
+		{
+			SECItem	   *buf;
+			char	   *cn;
+
+			if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+				continue;
+
+			/* Found a CN, decode and copy it into a newly allocated buffer */
+			buf = CERT_DecodeAVAValue(&(*ava)->value);
+			if (!buf)
+			{
+				/*
+				 * This failure case is difficult to test. (Since this code
+				 * runs after certificate authentication has otherwise
+				 * succeeded, you'd need to convince a CA implementation to
+				 * sign a corrupted certificate in order to get here.)
+				 *
+				 * Follow the behavior of CERT_GetCommonName() in this case and
+				 * simply return NULL, as if a Common Name had not been found.
+				 */
+				goto fail;
+			}
+
+			cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+			memcpy(cn, buf->data, buf->len);
+			cn[buf->len] = '\0';
+
+			*len = buf->len;
+
+			SECITEM_FreeItem(buf, PR_TRUE);
+			return cn;
+		}
+	}
+
+fail:
+	/* Not found  */
+	*len = 0;
+	return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+	SECStatus	status;
+	Port	   *port = (Port *) arg;
+	CERTCertificate *cert;
+	char	   *peer_cn;
+	unsigned int len;
+
+	status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+	if (status != SECSuccess)
+		return status;
+
+	port->peer_cn = NULL;
+	port->peer_cert_valid = false;
+
+	cert = SSL_PeerCertificate(port->pr_fd);
+	if (!cert)
+	{
+		/* Shouldn't be possible; why did we get a client cert callback? */
+		Assert(cert != NULL);
+
+		PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+		return SECFailure;
+	}
+
+	peer_cn = raw_subject_common_name(cert, &len);
+	if (!peer_cn)
+	{
+		/* No Common Name, but the certificate otherwise checks out. */
+		port->peer_cert_valid = true;
+
+		status = SECSuccess;
+		goto cleanup;
+	}
+
+	/*
+	 * Reject embedded NULLs in certificate common name to prevent attacks like
+	 * CVE-2009-4034.
+	 */
+	if (len != strlen(peer_cn))
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("SSL certificate's common name contains embedded null")));
+
+		pfree(peer_cn);
+		PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+		status = SECFailure;
+		goto cleanup;
+	}
+
+	port->peer_cn = peer_cn;
+	port->peer_cert_valid = true;
+
+	port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+	if  (!port->peer_dn)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("unable to get SSL certificate's distinguished name")));
+
+		pfree(peer_cn);
+		PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+		status = SECFailure;
+		goto cleanup;
+	}
+
+	status = SECSuccess;
+
+cleanup:
+	CERT_DestroyCertificate(cert);
+	return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+	/*
+	 * Disconnect our private Port from the fd before closing out the stack.
+	 * (Debug builds of NSPR will assert if we do not.)
+	 */
+	fd->secret = NULL;
+	return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+	const		PRIOMethods *default_methods;
+	PRFileDesc *layer;
+
+	/*
+	 * Start by initializing our layer with all the default methods so that we
+	 * can selectively override the ones we want while still ensuring that we
+	 * have a complete layer specification.
+	 */
+	default_methods = PR_GetDefaultIOMethods();
+	memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+	pr_iomethods.close = pg_ssl_close;
+
+	/*
+	 * Each IO layer must be identified by a unique name, where uniqueness is
+	 * per connection. Each connection in a postgres cluster can generate the
+	 * identity from the same string as they will create their IO layers on
+	 * different sockets. Only one layer per socket can have the same name.
+	 */
+	pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+	if (pr_id == PR_INVALID_IO_LAYER)
+	{
+		ereport(ERROR,
+				(errmsg("out of memory when setting up TLS connection")));
+		return NULL;
+	}
+
+	/*
+	 * Create the actual IO layer as a stub such that it can be pushed onto
+	 * the layer stack. The step via a stub is required as we define custom
+	 * callbacks.
+	 */
+	layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+	if (!layer)
+	{
+		ereport(ERROR,
+				(errmsg("unable to create NSS I/O layer")));
+		return NULL;
+	}
+
+	/* Store the Port as private data available in callbacks */
+	layer->secret = (void *) port;
+
+	return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ *			Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+	switch (v)
+	{
+			/*
+			 * There is no SSL_LIBRARY_ macro defined in NSS with the value
+			 * zero, so we use this to signal the caller that the highest
+			 * useful version should be set on the connection.
+			 */
+		case PG_TLS_ANY:
+			return 0;
+
+			/*
+			 * No guard is required here as there are no versions of NSS
+			 * without support for TLS1.
+			 */
+		case PG_TLS1_VERSION:
+			return SSL_LIBRARY_VERSION_TLS_1_0;
+		case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+			return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+			break;
+#endif
+		case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+			return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+			break;
+#endif
+		case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+			return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+			break;
+#endif
+		default:
+			break;
+	}
+
+	return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ *		Create and return a human readable error message given
+ *		the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+	return psprintf("%s (%s)",
+					PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+					PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ *		Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+	Port *port = (Port *) private_data;
+
+	/*
+	 * close_notify is not an error, but an indication that the connection
+	 * was orderly closed. Since the backend is only closing connections where
+	 * TLS is no longer in use, log an error in case this wasn't the case.
+	 * close_notify is defined in the NSS private enum SSL3AlertDescription
+	 * as zero.
+	 */
+	if (alert->description == 0)
+	{
+		if (port->ssl_in_use)
+			ereport(COMMERROR,
+					 errmsg("closing an active TLS connection"));
+		return;
+	}
+
+	/*
+	 * Any other alert received indicates an error, but since they can't be
+	 * made heads or tails with without reading the NSS source code let's
+	 * just log them as DEBUG information as they are of limited value in
+	 * any other circumstance.
+	 */
+	ereport(DEBUG2,
+			(errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ *		Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+	Port *port = (Port *) private_data;
+
+	if (!port || !port->ssl_in_use)
+		return SECSuccess;
+
+	/*
+	 * There is a connection still open, close it and signal to whatever that
+	 * called the shutdown that it was erroneous.
+	 */
+	be_tls_close(port);
+	be_tls_destroy();
+
+	return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool		ssl_passphrase_command_supports_reload;
 #ifdef USE_SSL
 bool		ssl_loaded_verify_locations = false;
 #endif
+char	   *ssl_database;
 
 /* GUC variable controlling SSL cipher list */
 char	   *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..5febe83efb 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4422,7 +4422,11 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&ssl_library,
 #ifdef USE_SSL
+#if defined(USE_OPENSSL)
 		"OpenSSL",
+#elif defined(USE_NSS)
+		"NSS",
+#endif
 #else
 		"",
 #endif
@@ -4490,6 +4494,16 @@ static struct config_string ConfigureNamesString[] =
 		check_canonical_path, assign_pgstat_temp_directory, NULL
 	},
 
+	{
+		{"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+			gettext_noop("Location of the NSS certificate database."),
+			NULL
+		},
+		&ssl_database,
+		"",
+		NULL, NULL, NULL
+	},
+
 	{
 		{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
 			gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4518,8 +4532,10 @@ static struct config_string ConfigureNamesString[] =
 			GUC_SUPERUSER_ONLY
 		},
 		&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
 		"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+		"",
 #else
 		"none",
 #endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ *	  NSS functionality shared between frontend and backend for working
+ *	  with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *        src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER	0xFFFF
+
+typedef struct
+{
+	SECOidTag	signature;
+	SECOidTag	hash;
+	int			len;
+}			NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+	{SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+	{SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+	{SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+	{SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+	{SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+	{SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+	{SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+	{SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+	{SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+	{SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+	{SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+	{SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+	{SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+	{SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+	{SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+	{0, 0}
+};
+
+typedef struct
+{
+	const char *name;
+	PRUint16	number;
+}			NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+	{"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+	{"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+	{"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+	{"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+	{"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+	{"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+	{"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+	{"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+	{"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+	{"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+	{"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+	{"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+	{"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+	{"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+	{"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+	{"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+	{"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+	{"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+	{"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+	{"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+	{"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+	{"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+	{"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+	{"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+	{"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+	{"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+	{"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+	{"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+	{"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+	{"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+	{"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+	{"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+	{"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+	{"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+	{"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+	{"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+	{"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+	{"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+	{"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+	{"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+	{NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ *			Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+	const		NSSCiphers *cipher_list;
+
+	for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+	{
+		if (pg_strcasecmp(cipher_list->name, name) == 0)
+		{
+			*cipher = cipher_list->number;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+	const NSSSignatureAlgorithm *candidate;
+
+	candidate = NSS_SCRAMDigestAlgorithm;
+
+	for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+	{
+		if (signature == candidate->signature)
+		{
+			*algorithm = candidate->hash;
+			*len = candidate->len;
+			return true;
+		}
+	}
+
+	return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ *	  NSS functionality shared between frontend and backend for working
+ *	  with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ *			Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+	switch (v)
+	{
+		/* SSL v2 and v3 are not supported */
+		case SSL_LIBRARY_VERSION_2:
+		case SSL_LIBRARY_VERSION_3_0:
+			Assert(false);
+			break;
+
+		case SSL_LIBRARY_VERSION_TLS_1_0:
+			return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+		case SSL_LIBRARY_VERSION_TLS_1_1:
+			return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+		case SSL_LIBRARY_VERSION_TLS_1_2:
+			return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+		case SSL_LIBRARY_VERSION_TLS_1_3:
+			return pstrdup("TLSv1.3");
+#endif
+	}
+
+	return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ *	  NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif							/* USE_NSS */
+
+#endif							/* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
 	bool		peer_cert_valid;
 
 	/*
-	 * OpenSSL structures. (Keep these last so that the locations of other
-	 * fields are the same whether or not you build with SSL enabled.)
+	 * TLS backend specific structures. (Keep these last so that the locations
+	 * of other fields are the same whether or not you build with SSL
+	 * enabled.)
 	 */
 #ifdef USE_OPENSSL
 	SSL		   *ssl;
 	X509	   *peer;
 #endif
+
+#ifdef USE_NSS
+	void	   *pr_fd;
+#endif
 } Port;
 
 #ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
  * This is not supported with old versions of OpenSSL that don't have
  * the X509_get_signature_nid() function.
  */
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
 #define HAVE_BE_TLS_GET_CERTIFICATE_HASH
 extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
 #endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
 typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
 extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
 #endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
 
 #endif							/* USE_SSL */
 
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
 #ifdef USE_SSL
 extern bool ssl_loaded_verify_locations;
 #endif
+extern char *ssl_database;
 
 extern int	secure_initialize(bool isServerStart);
 extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
  * USE_SSL code should be compiled only when compiling with an SSL
  * implementation.
  */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
 #define USE_SSL
 #endif
 
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
+	{"ssldatabase", NULL, NULL, NULL,
+		"CertificateDatabase", "", 64,
+	offsetof(struct pg_conn, ssldatabase)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..51f09fb7fa
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1200 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ *	  functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+										CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int	ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ *		"In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ *		implementation changes. In practice, this is unlikely to happen because
+ *		NSPR's implementation has been stable for years and because of NSPR's
+ *		strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif							/* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/*			 Procedures common to all secure sessions			*/
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+	/* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+	if (do_ssl)
+	{
+		conn->nss_context = NULL;
+
+		conn->ssl_in_use = false;
+		conn->has_password = false;
+	}
+
+	return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+	/*
+	 * All NSS references must be cleaned up before we close out the
+	 * context.
+	 */
+	if (conn->pr_fd)
+	{
+		PRStatus	status;
+
+		status = PR_Close(conn->pr_fd);
+		if (status != PR_SUCCESS)
+		{
+			pqInternalNotice(&conn->noticeHooks,
+							 "unable to close NSPR fd: %s",
+							 pg_SSLerrmessage(PR_GetError()));
+		}
+
+		conn->pr_fd = NULL;
+	}
+
+	if (conn->nss_context)
+	{
+		SECStatus	status;
+
+		/* The session cache must be cleared, or we'll leak references */
+		SSL_ClearSessionCache();
+
+		status = NSS_ShutdownContext(conn->nss_context);
+
+		if (status != SECSuccess)
+		{
+			pqInternalNotice(&conn->noticeHooks,
+							 "unable to shut down NSS context: %s",
+							 pg_SSLerrmessage(PR_GetError()));
+		}
+
+		conn->nss_context = NULL;
+	}
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+	SECStatus	status;
+	PRFileDesc *model;
+	NSSInitParameters params;
+	SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+	/* This locking is modelled after fe-secure-openssl.c */
+	if (ssl_config_mutex == NULL)
+	{
+		while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+			/* loop while another thread owns the lock */ ;
+		if (ssl_config_mutex == NULL)
+		{
+			if (pthread_mutex_init(&ssl_config_mutex, NULL))
+			{
+				printfPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("unable to lock thread"));
+				return PGRES_POLLING_FAILED;
+			}
+		}
+		InterlockedExchange(&win32_ssl_create_mutex, 0);
+	}
+#endif
+	if (pthread_mutex_lock(&ssl_config_mutex))
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to lock thread"));
+		return PGRES_POLLING_FAILED;
+	}
+#endif							/* ENABLE_THREAD_SAFETY */
+
+	/*
+	 * The NSPR documentation states that runtime initialization via PR_Init
+	 * is no longer required, as the first caller into NSPR will perform the
+	 * initialization implicitly. See be-secure-nss.c for further discussion
+	 * on PR_Init.
+	 */
+	PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+	/*
+	 * NSS initialization and the use of contexts is further discussed in
+	 * be-secure-nss.c
+	 */
+	memset(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+	params.dbTokenDescription = strdup("postgres");
+
+	if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+	{
+		char	   *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+		conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+											&params,
+											NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+		pfree(ssldatabase_path);
+
+		if (!conn->nss_context)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("unable to open certificate database \"%s\": %s"),
+							  conn->ssldatabase,
+							  pg_SSLerrmessage(PR_GetError()));
+			return PGRES_POLLING_FAILED;
+		}
+	}
+	else
+	{
+		conn->nss_context = NSS_InitContext("", "", "", "", &params,
+											NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+											NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+											NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+		if (!conn->nss_context)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("unable to initialize NSS: %s"),
+							  pg_SSLerrmessage(PR_GetError()));
+			return PGRES_POLLING_FAILED;
+		}
+	}
+
+	/*
+	 * Configure cipher policy by setting the domestic suite.
+	 *
+	 * Historically there were different cipher policies based on export (and
+	 * import) restrictions: Domestic, Export and France. These are since long
+	 * removed with all ciphers being enabled by default. Due to backwards
+	 * compatibility, the old API is still used even though all three policies
+	 * now do the same thing.
+	 */
+	status = NSS_SetDomesticPolicy();
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to configure cipher policy: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * If we don't have a certificate database, the system trust store is the
+	 * fallback we can use. If we fail to initialize that as well, we can
+	 * still attempt a connection as long as the sslmode isn't verify-*.
+	 */
+	if (!conn->ssldatabase &&
+		(strcmp(conn->sslmode, "verify-ca") == 0 ||
+		 strcmp(conn->sslmode, "verify-full") == 0))
+	{
+		status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+		if (status != SECSuccess)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+							  ca_trust_name,
+							  pg_SSLerrmessage(PR_GetError()));
+
+			return PGRES_POLLING_FAILED;
+		}
+	}
+
+#ifdef ENABLE_THREAD_SAFETY
+	pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+	PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+	/*
+	 * Import the already opened socket as we don't want to use NSPR functions
+	 * for opening the network socket due to how the PostgreSQL protocol works
+	 * with TLS connections. This function is not part of the NSPR public API,
+	 * see the comment at the top of the file for the rationale of still using
+	 * it.
+	 */
+	conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+	if (!conn->pr_fd)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to attach to socket: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * Most of the documentation available, and implementations of, NSS/NSPR
+	 * use the PR_NewTCPSocket() function here, which has the drawback that it
+	 * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+	 * copes with IPv6 as well.
+	 */
+	model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+	if (!model)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to enable TLS: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/* Disable old protocol versions (SSLv2 and SSLv3) */
+	SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+	SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+	SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+	/*
+	 * Enable protection against the BEAST attack in case the NSS library has
+	 * support for that. While SSLv3 is disabled, we may still allow TLSv1
+	 * which is affected. The option isn't documented as an SSL option, but as
+	 * an NSS environment variable.
+	 */
+	SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+	/* Set us up as a TLS client for the handshake */
+	SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+	/*
+	 * When setting the available protocols, we either use the user defined
+	 * configuration values, and if missing we accept whatever is the highest
+	 * version supported by the library as the max and only limit the range in
+	 * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+	 * for Stream protocols, rather than datagram.
+	 */
+	SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+	desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+	if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+	{
+		int			ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+		if (ssl_min_ver == -1)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+							  conn->ssl_min_protocol_version);
+			return -1;
+		}
+
+		desired_range.min = ssl_min_ver;
+	}
+
+	if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+	{
+		int			ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+		if (ssl_max_ver == -1)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+							  conn->ssl_max_protocol_version);
+			return -1;
+		}
+
+		desired_range.max = ssl_max_ver;
+	}
+
+	if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * Set up callback for verifying server certificates, as well as for how
+	 * to handle failed verifications.
+	 */
+	SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+	SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+	/*
+	 * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+	 * operation is critical as NSS SSL_* functions may return SECSuccess on
+	 * the socket even though SSL hasn't been enabled, which introduce a risk
+	 * of silent downgrades.
+	 */
+	conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+	if (!conn->pr_fd)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to configure client for TLS: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * The model can now be closed as we've applied the settings of the model
+	 * onto the real socket. From here on we should only use conn->pr_fd.
+	 */
+	PR_Close(model);
+
+	/* Set the private data to be passed to the password callback */
+	SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+	status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to initiate handshake: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/* Set callback for client authentication when requested by the server */
+	SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+	/*
+	 * Specify which hostname we are expecting to talk to for the ClientHello
+	 * SNI extension, unless the user has disabled it. NSS will suppress IP
+	 * addresses in SNIs, so we don't need to filter those explicitly like we do
+	 * for OpenSSL.
+	 */
+	if (conn->sslsni && conn->sslsni[0] == '1')
+	{
+		const char *host = conn->connhost[conn->whichhost].host;
+		if (host && host[0])
+			SSL_SetURL(conn->pr_fd, host);
+	}
+
+	status = SSL_ForceHandshake(conn->pr_fd);
+
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("SSL error: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	conn->ssl_in_use = true;
+	return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+	PRInt32		nread;
+	PRErrorCode status;
+	int			read_errno = 0;
+
+	/*
+	 * PR_Recv blocks until there is data to read or the timeout expires. We
+	 * don't want to sit blocked here, so the timeout is turned off by using
+	 * PR_INTERVAL_NO_WAIT which means "return immediately",  Zero is returned
+	 * for closed connections, while -1 indicates an error within the ongoing
+	 * connection.
+	 */
+	nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+	if (nread == 0)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("TLS read error: EOF detected\n"));
+		read_errno = ECONNRESET;
+		nread = -1;
+	}
+	else if (nread == -1)
+	{
+		status = PR_GetError();
+
+		switch (status)
+		{
+			case PR_IO_TIMEOUT_ERROR:
+				/* No data available yet. */
+				nread = 0;
+				break;
+
+			case PR_WOULD_BLOCK_ERROR:
+				read_errno = EWOULDBLOCK;
+				break;
+
+				/*
+				 * The error cases for PR_Recv are not documented, but can be
+				 * reverse engineered from _MD_unix_map_default_error() in the
+				 * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+				 */
+			case PR_NETWORK_UNREACHABLE_ERROR:
+			case PR_CONNECT_RESET_ERROR:
+				read_errno = ECONNRESET;
+				break;
+
+			default:
+				break;
+		}
+
+		if (nread == -1)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("TLS read error: %s"),
+							  pg_SSLerrmessage(status));
+		}
+	}
+
+	SOCK_ERRNO_SET(read_errno);
+	return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ *		Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ *     PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+	return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ *		Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+	PRInt32		n;
+	PRErrorCode status;
+	int			write_errno = 0;
+
+	n = PR_Write(conn->pr_fd, ptr, len);
+
+	if (n < 0)
+	{
+		status = PR_GetError();
+
+		switch (status)
+		{
+			case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+				write_errno = EAGAIN;
+#else
+				write_errno = EINTR;
+#endif
+				break;
+
+			default:
+				printfPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("TLS write error: %s"),
+								  pg_SSLerrmessage(status));
+				write_errno = ECONNRESET;
+				break;
+		}
+	}
+
+	SOCK_ERRNO_SET(write_errno);
+	return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+	CERTCertificate *server_cert;
+	SECOidTag	signature_tag;
+	SECOidTag	digest_alg;
+	int			digest_len;
+	PLArenaPool *arena = NULL;
+	SECItem		digest;
+	char	   *ret = NULL;
+	SECStatus	status;
+
+	*len = 0;
+
+	server_cert = SSL_PeerCertificate(conn->pr_fd);
+	if (!server_cert)
+		goto cleanup;
+
+	signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+	if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("could not find digest for OID '%s'\n"),
+						  SECOID_FindOIDTagDescription(signature_tag));
+		goto cleanup;
+	}
+
+	arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+	digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+	if (!digest.data)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("out of memory"));
+		goto cleanup;
+	}
+	digest.len = digest_len;
+
+	status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+						  server_cert->derCert.len);
+
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to generate peer certificate digest: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		goto cleanup;
+	}
+
+	ret = pg_malloc(digest.len);
+	memcpy(ret, digest.data, digest.len);
+	*len = digest_len;
+
+cleanup:
+	if (arena)
+		PORT_FreeArena(arena, PR_TRUE);
+	if (server_cert)
+		CERT_DestroyCertificate(server_cert);
+
+	return ret;
+}
+
+/*
+ *	Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+												int *names_examined,
+												char **first_name)
+{
+	char	   *server_hostname = NULL;
+	CERTCertificate *server_cert = NULL;
+	SECStatus	status = SECSuccess;
+	SECItem		altname_item;
+	PLArenaPool *arena = NULL;
+	CERTGeneralName *san_list;
+	CERTGeneralName *cn;
+
+	/*
+	 * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+	 * hostname. In our case, SSL_SetURL() won't have been called if the user
+	 * has disabled SNI, so we always pull the hostname from connhost instead.
+	 */
+	server_hostname = conn->connhost[conn->whichhost].host;
+	if (!server_hostname || server_hostname[0] == '\0')
+	{
+		/* verify-full shouldn't progress this far without a hostname. */
+		status = SECFailure;
+		goto done;
+	}
+
+	/*
+	 * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+	 * verification.
+	 */
+	server_cert = SSL_PeerCertificate(conn->pr_fd);
+	status = CERT_VerifyCertName(server_cert, server_hostname);
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to verify server hostname: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		goto done;
+	}
+
+	status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+									&altname_item);
+	if (status == SECSuccess)
+	{
+		arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+		if (!arena)
+		{
+			status = SECFailure;
+			goto done;
+		}
+		san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+		if (!san_list)
+		{
+			status = SECFailure;
+			goto done;
+		}
+
+		for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+		{
+			char	   *alt_name;
+			int			rv;
+			char		tmp[512];
+
+			status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+												 (char *) cn->name.other.data,
+												 cn->name.other.len);
+
+			if (status != SECSuccess)
+				goto done;
+
+			rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+															  strlen(tmp),
+															  &alt_name);
+			if (alt_name)
+			{
+				if (!*first_name)
+					*first_name = alt_name;
+				else
+					free(alt_name);
+			}
+
+			if (rv == 1)
+				status = SECSuccess;
+			else
+			{
+				status = SECFailure;
+				break;
+			}
+		}
+	}
+	else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+		status = SECSuccess;
+	else
+		status = SECSuccess;
+
+done:
+	/* san_list will be freed by freeing the arena it was allocated in */
+	if (arena)
+		PORT_FreeArena(arena, PR_TRUE);
+	if (server_cert)
+		CERT_DestroyCertificate(server_cert);
+
+	if (status == SECSuccess)
+		return 1;
+
+	return 0;
+}
+
+/* ------------------------------------------------------------ */
+/*			PostgreSQL specific TLS support functions			*/
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+	const char *error;
+
+	/*
+	 * Try to get the user friendly error description, and if that fails try
+	 * to fall back on the name of the PRErrorCode.
+	 */
+	error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+	if (!error)
+		error = PR_ErrorToName(errcode);
+	if (error)
+		return error;
+
+	return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+	SECMODModule *mod;
+	char	   *modulespec;
+
+	modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+	/*
+	 * Attempt to load the specified module. The second parameter is "parent"
+	 * which should always be NULL for application code. The third parameter
+	 * defines if loading should recurse which is only applicable when loading
+	 * a module from within another module. This hierarchy would have to be
+	 * defined in the modulespec, and since we don't support anything but
+	 * directly addressed modules we should pass PR_FALSE.
+	 */
+	mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+	pfree(modulespec);
+
+	if (mod && mod->loaded)
+	{
+		*module = mod;
+		return SECSuccess;
+	}
+
+	SECMOD_DestroyModule(mod);
+	return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/*						NSS Callbacks							*/
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ *			Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+	SECStatus	status;
+	PGconn	   *conn = (PGconn *) arg;
+	CERTCertificate *server_cert;
+	void	   *pin;
+
+	Assert(!isServer);
+
+	pin = SSL_RevealPinArg(conn->pr_fd);
+	server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+	/*
+	 * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+	 * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+	 * does not impact NSS connections as any CRL in the NSS database will be
+	 * used automatically.
+	 */
+	status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+									   server_cert,
+									   checksig,
+									   certificateUsageSSLServer,
+									   pin,
+									   NULL);
+
+	/*
+	 * If we've already failed validation then there is no point in also
+	 * performing the hostname check for verify-full.
+	 */
+	if (status == SECSuccess)
+	{
+		if (!pq_verify_peer_name_matches_certificate(conn))
+			status = SECFailure;
+	}
+
+	CERT_DestroyCertificate(server_cert);
+	return status;
+}
+
+/*
+ * pg_client_auth_handler
+ *		Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+					   CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+	PGconn	   *conn = (PGconn *) arg;
+	char	   *nnptr = NULL;
+	char		nickname[MAXPGPATH];
+
+	/*
+	 * If we have an sslcert configured we use that combined with the token-
+	 * name as the nickname. When no sslcert is set we pass in NULL and let
+	 * NSS try to find the certificate among the ones present in the database.
+	 * Without an sslcert we cannot set the database token since NSS parses
+	 * the given string expecting to find a certificate nickname after the
+	 * colon.
+	 */
+	if (conn->sslcert)
+	{
+		snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+		nnptr = nickname;
+	}
+
+	return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ *		Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+	PGconn	   *conn = (PGconn *) arg;
+	PRErrorCode err;
+
+	/*
+	 * This really shouldn't happen, as we've set the PGconn object as our
+	 * callback data, and at the callsite we know it will be populated. That
+	 * being said, the NSS code itself performs this check even when it should
+	 * not be required so let's use the same belts with our suspenders.
+	 */
+	if (!arg)
+		return SECFailure;
+
+	err = PORT_GetError();
+
+	/*
+	 * For sslmodes other than verify-full and verify-ca we don't perform peer
+	 * validation, so return immediately.  sslmode require with a database
+	 * specified which contains a CA certificate will work like verify-ca to
+	 * be compatible with the OpenSSL implementation.
+	 */
+	if (strcmp(conn->sslmode, "require") == 0)
+	{
+		if (conn->ssldatabase &&
+			strlen(conn->ssldatabase) > 0 &&
+			certificate_database_has_CA(conn))
+			goto failure;
+	}
+	else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+			 strcmp(conn->sslmode, "verify-ca") == 0)
+		goto failure;
+
+	/*
+	 * TODO: these are relevant error codes that can occur in certificate
+	 * validation, figure out which we don't want for require/prefer etc.
+	 */
+	switch (err)
+	{
+		case SEC_ERROR_INVALID_AVA:
+		case SEC_ERROR_INVALID_TIME:
+		case SEC_ERROR_BAD_SIGNATURE:
+		case SEC_ERROR_EXPIRED_CERTIFICATE:
+		case SEC_ERROR_UNKNOWN_ISSUER:
+		case SEC_ERROR_UNTRUSTED_ISSUER:
+		case SEC_ERROR_UNTRUSTED_CERT:
+		case SEC_ERROR_CERT_VALID:
+		case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+		case SEC_ERROR_CRL_EXPIRED:
+		case SEC_ERROR_CRL_BAD_SIGNATURE:
+		case SEC_ERROR_EXTENSION_VALUE_INVALID:
+		case SEC_ERROR_CA_CERT_INVALID:
+		case SEC_ERROR_CERT_USAGES_INVALID:
+		case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+			break;
+		default:
+			goto failure;
+			break;
+	}
+
+	return SECSuccess;
+
+failure:
+	printfPQExpBuffer(&conn->errorMessage,
+					  libpq_gettext("unable to verify certificate: %s"),
+					  pg_SSLerrmessage(err));
+
+	return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/*					SSL information functions					*/
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+	return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+	if (!conn)
+		return NULL;
+
+	/*
+	 * Return the underlying PRFileDesc which can be used to access
+	 * information on the connection details. There is no SSL context per se.
+	 */
+	if (strcmp(struct_name, "NSS") == 0)
+		return conn->pr_fd;
+	return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+	static const char *const result[] = {
+		"library",
+		"cipher",
+		"protocol",
+		"key_bits",
+		"compression",
+		NULL
+	};
+
+	return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+	SSLCipherSuiteInfo suite;
+
+	if (!conn || !conn->pr_fd)
+		return NULL;
+
+	if (strcmp(attribute_name, "library") == 0)
+		return "NSS";
+
+	status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+		return NULL;
+
+	status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+	if (status != SECSuccess)
+		return NULL;
+
+	if (strcmp(attribute_name, "cipher") == 0)
+		return suite.cipherSuiteName;
+
+	if (strcmp(attribute_name, "key_bits") == 0)
+	{
+		static char key_bits_str[8];
+
+		snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+		return key_bits_str;
+	}
+
+	if (strcmp(attribute_name, "protocol") == 0)
+		return ssl_protocol_version_to_string(channel.protocolVersion);
+
+	/*
+	 * NSS disabled support for compression in version 3.33, and it was only
+	 * available for SSLv3 at that point anyways, so we can safely return off
+	 * here without even checking.
+	 */
+	if (strcmp(attribute_name, "compression") == 0)
+		return "off";
+
+	return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+	if (pg_strcasecmp("TLSv1", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+	if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+	if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+	if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+	return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ *		 Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+	CERTCertList *certificates;
+	bool		hasCA;
+
+	/*
+	 * If the certificate database has a password we must provide it, since
+	 * this API doesn't invoke the standard password callback.
+	 */
+	if (conn->has_password)
+		certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+	else
+		certificates = PK11_ListCerts(PK11CertListCA, NULL);
+	hasCA = !CERT_LIST_EMPTY(certificates);
+	CERT_DestroyCertList(certificates);
+
+	return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+	return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+	PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ *		 Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+	PGconn	   *conn = (PGconn *) arg;
+
+	conn->has_password = true;
+
+	if (PQsslKeyPassHook)
+		return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+	else
+		return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * 		 Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+	PGconn	   *conn = (PGconn *) arg;
+
+	/*
+	 * If the password didn't work the first time there is no point in
+	 * retrying as it hasn't changed.
+	 */
+	if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+		return PORT_Strdup(conn->sslpassword);
+
+	return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
 }
 #endif							/* USE_OPENSSL */
 
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+	return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+	return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+	return NULL;
+}
+#endif							/* USE_NSS */
+
 /* Dummy version of GSSAPI information functions, when built without GSS support */
 #ifndef ENABLE_GSS
 
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
 extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
 extern int	PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
 
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
 #endif
 #endif							/* USE_OPENSSL */
 
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
 /*
  * POSTGRES backend dependent Constants.
  */
@@ -385,6 +389,7 @@ struct pg_conn
 	char	   *sslcrl;			/* certificate revocation list filename */
 	char	   *sslcrldir;		/* certificate revocation list directory name */
 	char	   *sslsni;			/* use SSL SNI extension (0 or 1) */
+	char	   *ssldatabase;	/* NSS certificate/key database */
 	char	   *requirepeer;	/* required peer credentials for local sockets */
 	char	   *gssencmode;		/* GSS mode (require,prefer,disable) */
 	char	   *krbsrvname;		/* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
 								 * removed as this locking is handled
 								 * internally in OpenSSL >= 1.1.0. */
 #endif							/* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+	NSSInitContext	   *nss_context;	/* NSS connection specific context */
+	PRFileDesc		   *pr_fd;			/* NSPR file descriptor for the connection */
+
+	/*
+	 * Track whether the NSS database has a password set or not. There is no
+	 * API function for retrieving password status of a database, but we need
+	 * to know since some NSS API calls require the password passed in (they
+	 * don't call the callback themselves). To track, we simply flip this to
+	 * true in case NSS invoked the password callback - as that will only
+	 * happen in case there is a password. The reason for tracking this is
+	 * that there are calls which require a password parameter, but doesn't
+	 * use the callbacks provided, so we must call the callback on behalf of
+	 * these.
+	 */
+	bool		has_password;
+#endif							/* USE_NSS */
 #endif							/* USE_SSL */
 
 #ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
  * This is not supported with old versions of OpenSSL that don't have
  * the X509_get_signature_nid() function.
  */
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
 #define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
 extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
 #endif
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0002-Refactor-SSL-testharness-for-multiple-library.patch (11.5K, ../[email protected]/3-v44-0002-Refactor-SSL-testharness-for-multiple-library.patch)
  download | inline diff:
From 173177aa000abad6f82ca76a5b8fc03865578dd7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v44 02/10] Refactor SSL testharness for multiple library

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

No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
 src/test/ssl/t/001_ssltests.pl                |  56 ++--------
 src/test/ssl/t/002_scram.pl                   |   6 +-
 src/test/ssl/t/SSL/Backend/OpenSSL.pm         | 103 ++++++++++++++++++
 .../ssl/t/{SSLServer.pm => SSL/Server.pm}     |  83 +++++++++++---
 4 files changed, 181 insertions(+), 67 deletions(-)
 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/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
 use TestLib;
 use Test::More;
 
-use File::Copy;
-
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_ssl} ne 'openssl')
 {
@@ -35,32 +33,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", "client-dn");
-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";
@@ -75,32 +47,22 @@ $node->start;
 
 # Run this before we lock down access below.
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
 
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	'trust');
 
 note "testing password-protected keys";
 
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				   'server-password', 'echo wrongpassword');
 command_fails(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart fails with password-protected key file with wrong password');
 $node->_update_pid(0);
 
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				'server-password', 'echo secret1');
 command_ok(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
 	expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
 
 # 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 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_ssl} ne 'openssl')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	plan skip_all => 'SSL not supported by this build';
 }
 
 # This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
 
 # clean up
 unlink($client_tmp_key);
-
-done_testing($number_of_tests);
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..e77ee25cc7
--- /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", "client-dn");
+}
+
+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.
+	my @keys = (
+		"client",               "client-revoked",
+		"client-der",           "client-encrypted-pem",
+		"client-encrypted-der", "client-dn");
+	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';
+}
+
+# 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 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,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
 );
 
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
 	close $sslconf;
 
 	# Copy all server certificates and keys, and client root cert, to the data dir
-	copy_files("ssl/server-*.crt", $pgdata);
-	copy_files("ssl/server-*.key", $pgdata);
-	chmod(0600, glob "$pgdata/server-*.key") or die $!;
-	copy_files("ssl/root+client_ca.crt", $pgdata);
-	copy_files("ssl/root_ca.crt",        $pgdata);
-	copy_files("ssl/root+client.crl",    $pgdata);
-	mkdir("$pgdata/root+client-crldir");
-	copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+	if (defined($openssl))
+	{
+		copy_files("ssl/server-*.crt", $pgdata);
+		copy_files("ssl/server-*.key", $pgdata);
+		chmod(0600, glob "$pgdata/server-*.key") or die $!;
+		copy_files("ssl/root+client_ca.crt", $pgdata);
+		copy_files("ssl/root_ca.crt",        $pgdata);
+		copy_files("ssl/root+client.crl",    $pgdata);
+		mkdir("$pgdata/root+client-crldir");
+		copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+	}
+	elsif (defined($nss))
+	{
+		RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+	}
 
 	# Stop and restart server to load new listen_addresses.
 	$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
 	# Change pg_hba after restart because hostssl requires ssl=on
 	configure_hba_for_ssl($node, $servercidr, $authmethod);
 
+	# Finally, perform backend specific configuration
+	$backend->init();
+
 	return;
 }
 
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+	return $backend->get_library();
+}
+
+sub cleanup
+{
+	$backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
 {
 	my $node     = $_[0];
 	my $certfile = $_[1];
 	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || '';
+	my $pwcmd    = $_[4] || '';
 	my $crlfile  = "root+client.crl";
 	my $crldir;
 	my $pgdata = $node->data_dir;
 
+	$keyfile = $certfile if $keyfile eq '';
+
 	# defaults to use crl file
 	if (defined $_[3] || defined $_[4])
 	{
@@ -150,13 +193,23 @@ sub switch_server_cert
 
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	print $sslconf "ssl=on\n";
-	print $sslconf "ssl_ca_file='$cafile.crt'\n";
-	print $sslconf "ssl_cert_file='$certfile.crt'\n";
-	print $sslconf "ssl_key_file='$certfile.key'\n";
+	print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
 	print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
 	print $sslconf "ssl_crl_dir='$crldir'\n"   if defined $crldir;
+	print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+	  unless $pwcmd eq '';
 	close $sslconf;
+	return;
+}
 
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+	my $node = $_[0];
+	set_server_cert(@_);
 	$node->restart;
 	return;
 }
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0003-nss-Add-NSS-specific-tests.patch (57.8K, ../[email protected]/4-v44-0003-nss-Add-NSS-specific-tests.patch)
  download | inline diff:
From 2103ccfaeabf4c68e43eb3cf7c473c73f7316213 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v44 03/10] 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                 | 246 ++++++++++++++++
 src/test/ssl/t/001_ssltests.pl        | 395 +++++++++++++++++---------
 src/test/ssl/t/002_scram.pl           |  33 ++-
 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          |  46 +--
 6 files changed, 610 insertions(+), 190 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 c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ 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/server-cn-only.crt__server-cn-only.key.crldir.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 +71,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 +102,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
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
 	openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
 	rm ssl/server-$*.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
+	cp -R $< $@
+	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'
@@ -92,6 +237,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+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 ''
+
+# 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^#+' > [email protected]
+	certutil -d "sql:$@" -N -f [email protected]
+	certutil -d "sql:$@" -A -f [email protected] -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -f [email protected] -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -f [email protected] -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 [email protected]
+
 # 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
@@ -99,6 +265,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
@@ -131,19 +305,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
@@ -169,14 +364,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 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,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 => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	$nss = 1;
+	plan tests => 112;
 }
 else
 {
-	plan tests => 110;
+	plan skip_all => 'SSL not supported by this build';
 }
 
 #### Some configuration
@@ -54,19 +63,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",
+		"connect to server with incorrect key password configured",
+		expected_stderr => qr/\QSSL error\E/);
+
+	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.
@@ -94,7 +152,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";
@@ -113,87 +171,104 @@ $node->connect_ok(
 $node->connect_fails(
 	"$common_connstr sslrootcert=invalid sslmode=verify-ca",
 	"connect without server root cert sslmode=verify-ca",
-	expected_stderr => qr/root certificate file "invalid" does not exist/);
+	expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
 $node->connect_fails(
 	"$common_connstr sslrootcert=invalid sslmode=verify-full",
 	"connect without server root cert sslmode=verify-full",
-	expected_stderr => qr/root certificate file "invalid" does not exist/);
+	expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
 
 # 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",
 	"connect with wrong server root cert sslmode=require",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+	expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
 $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",
 	"connect with wrong server root cert sslmode=verify-ca",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+	expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
 $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",
 	"connect with wrong server root cert sslmode=verify-full",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+	expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
 
-# 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",
-	"connect with server CA cert, without root CA",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+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",
+		"connect with server CA cert, without root CA",
+		expected_stderr => qr/SSL error: certificate verify failed/);
+}
 
 # 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",
-	"CRL belonging to a different CA",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+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",
+		"CRL belonging to a different CA",
+		expected_stderr => qr/SSL error: certificate verify failed/);
 
-# The same for CRL directory
-$node->connect_fails(
-	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
-	"directory CRL belonging to a different CA",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+	# The same for CRL directory
+	$node->connect_fails(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+		"directory CRL belonging to a different CA",
+		expected_stderr => qr/SSL error: certificate verify failed/);
+}
 
 # 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");
@@ -204,14 +279,14 @@ $node->connect_fails(
 	"$common_connstr sslmode=verify-full host=wronghost.test",
 	"mismatch between host name and server certificate sslmode=verify-full",
 	expected_stderr =>
-	  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/
 );
 
 # 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",
@@ -226,21 +301,22 @@ $node->connect_fails(
 	"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
 	"host name not matching with X.509 Subject Alternative Names",
 	expected_stderr =>
-	  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/
 );
+
 $node->connect_fails(
 	"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
 	"host name not matching with X.509 Subject Alternative Names wildcard",
 	expected_stderr =>
-	  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/
 );
 
 # 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",
@@ -250,21 +326,21 @@ $node->connect_fails(
 	"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
 	"host name not matching with a single X.509 Subject Alternative Name",
 	expected_stderr =>
-	  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/
 );
 $node->connect_fails(
 	"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
 	"host name not matching with a single X.509 Subject Alternative Name wildcard",
 	expected_stderr =>
-	  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/,
 );
 
 # 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");
@@ -274,43 +350,43 @@ $node->connect_fails(
 	"$common_connstr host=common-name.pg-ssltest.test",
 	"certificate with both a CN and SANs ignores CN",
 	expected_stderr =>
-	  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/
 );
 
 # 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",
+	. "sslmode=verify-full host=common-name.pg-ssltest.test",
 	"server certificate without CN or SANs sslmode=verify-full",
 	expected_stderr =>
-	  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./);
 
 # 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/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
 	"does not connect with client-side CRL file",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+	expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
 $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",
 	"does not connect with client-side CRL directory",
-	expected_stderr => qr/SSL error: certificate verify failed/);
+	expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
 
 # pg_stat_ssl
 command_like(
@@ -328,29 +404,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",
 	"connection failure with incorrect range of TLS protocol versions",
 	expected_stderr => qr/invalid SSL protocol version range/);
 $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",
 	"connection failure with an incorrect SSL protocol minimum bound",
 	expected_stderr => qr/invalid ssl_min_protocol_version value/);
 $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",
 	"connection failure with an incorrect SSL protocol maximum bound",
 	expected_stderr => qr/invalid ssl_max_protocol_version value/);
 
+# 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(
@@ -364,62 +457,82 @@ $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"
-);
+SKIP:
+{
+	skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+	# correct client cert in unencrypted PEM without a nickname (sslcert)
+	$node->connect_ok(
+		"$common_connstr user=ssltestuser",
+		"certificate authorization succeeds without correct client cert in connstring"
+	);
+}
+
+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'",
+	"$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",
 	"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
 	expected_stderr =>
-	  qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+	  qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
 );
 
-
-# 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",
-	log_like => [
-		qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
-	],);
-
-# 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 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",
-	# the full DN should still be used as the authenticated identity
-	log_like => [
-		qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
-	],);
-
-
+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",
+		log_like => [
+			qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+		],);
+
+	# 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",
+		# the full DN should still be used as the authenticated identity
+		log_like => [
+			qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+		],);
+
+	# 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:
 {
@@ -457,18 +570,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",
@@ -483,16 +597,20 @@ $node->connect_fails(
 	"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"certificate authorization fails with client cert belonging to another user",
 	expected_stderr =>
-	  qr/certificate authentication failed for user "anotheruser"/,
+	  qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
 	# certificate authentication should be logged even on failure
 	log_like =>
 	  [qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
 
+$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",
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+	expected_stderr =>
+	  qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
 	# revoked certificates should not authenticate the user
 	log_unlike => [qr/connection authenticated:/],);
 
@@ -500,7 +618,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",
@@ -525,9 +643,9 @@ $node->connect_ok(
 	log_unlike => [qr/connection authenticated:/],);
 
 # 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",
@@ -535,17 +653,18 @@ $node->connect_ok(
 $node->connect_fails(
 	$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
 	"intermediate client certificate is missing",
-	expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+	expected_stderr =>
+	  qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
 
 # 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",
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+	expected_stderr =>
+	  qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
 
 # clean up
 
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,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 ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+	$nss = 1;
+	$supports_tls_server_end_point = 1;
+	plan tests => 11;
+}
+else
 {
 	plan skip_all => 'SSL not supported by this build';
 }
@@ -26,12 +45,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 ? 11 : 12;
-
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
 
@@ -50,7 +63,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";
@@ -99,7 +112,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",
 	"Cert authentication and channel_binding=require",
 	expected_stderr =>
 	  qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
 
 # Certificate verification at the connection level should still work fine.
 $node->connect_ok(
-	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
 	"SCRAM with clientcert=verify-full",
 	log_like => [
 		qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
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 e77ee25cc7..9e419b0e0f 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 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
 use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
-  set_server_cert
   switch_server_cert
 );
 
@@ -170,46 +169,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 $node   = shift;
+	my %params = @_;
 	my $pgdata = $node->data_dir;
 
-	$keyfile = $certfile if $keyfile eq '';
-
-	# defaults to use crl file
-	if (defined $_[3] || defined $_[4])
-	{
-		$crlfile = $_[3];
-		$crldir  = $_[4];
-	}
-
 	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.24.3 (Apple Git-128)



  [application/octet-stream] v44-0004-test-check-for-empty-stderr-during-connect_ok.patch (3.6K, ../[email protected]/5-v44-0004-test-check-for-empty-stderr-during-connect_ok.patch)
  download | inline diff:
From 9482c136f352d0c6420f1c352025759c0ac141ad Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v44 04/10] test: check for empty stderr during connect_ok()

...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
 src/test/authentication/t/001_password.pl | 2 +-
 src/test/authentication/t/002_saslprep.pl | 2 +-
 src/test/kerberos/t/001_auth.pl           | 2 +-
 src/test/ldap/t/001_auth.pl               | 2 +-
 src/test/perl/PostgresNode.pm             | 5 ++++-
 src/test/ssl/t/001_ssltests.pl            | 4 ++--
 src/test/ssl/t/002_scram.pl               | 4 ++--
 7 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 16570a4e2c..bfea8bb8ae 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
 }
 else
 {
-	plan tests => 23;
+	plan tests => 32;
 }
 
 
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index acd379df31..9457277094 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
 }
 else
 {
-	plan tests => 12;
+	plan tests => 20;
 }
 
 # Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index c484237d07..a5afdc8680 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
 
 if ($ENV{with_gssapi} eq 'yes')
 {
-	plan tests => 44;
+	plan tests => 54;
 }
 else
 {
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index f670bc5e0d..d7f49739dd 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
 
 if ($ENV{with_ldap} eq 'yes')
 {
-	plan tests => 28;
+	plan tests => 40;
 }
 else
 {
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
 
 	if (defined($params{expected_stdout}))
 	{
-		like($stdout, $params{expected_stdout}, "$test_name: matches");
+		like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
 	}
+
+	is($stderr, "", "$test_name: no stderr");
+
 	if (@log_like or @log_unlike)
 	{
 		my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
 if ($ENV{with_ssl} eq 'openssl')
 {
 	$openssl = 1;
-	plan tests => 112;
+	plan tests => 144;
 }
 elsif ($ENV{with_ssl} eq 'nss')
 {
 	$nss = 1;
-	plan tests => 112;
+	plan tests => 138;
 }
 else
 {
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
 	# 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 ? 11 : 12;
+	plan tests => 15;
 }
 elsif ($ENV{with_ssl} eq 'nss')
 {
 	$nss = 1;
 	$supports_tls_server_end_point = 1;
-	plan tests => 11;
+	plan tests => 15;
 }
 else
 {
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0005-nss-pg_strong_random-support.patch (2.0K, ../[email protected]/6-v44-0005-nss-pg_strong_random-support.patch)
  download | inline diff:
From dfda535956a1b7b5a73bf5157bfa57eb02ed4416 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v44 05/10] nss: pg_strong_random support

This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
 src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
 1 file changed, 50 insertions(+), 2 deletions(-)

diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
 	return false;
 }
 
-#else							/* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+	/* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+	NSSInitParameters params;
+	NSSInitContext *nss_context;
+	SECStatus	status;
+
+	memset(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+	nss_context = NSS_InitContext("", "", "", "", &params,
+								  NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+								  NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+								  NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+	if (!nss_context)
+		return false;
+
+	status = PK11_GenerateRandom(buf, len);
+	NSS_ShutdownContext(nss_context);
+
+	if (status == SECSuccess)
+		return true;
+
+	return false;
+}
+
+#else							/* not USE_OPENSSL, USE_NSS or WIN32 */
 
 /*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
  */
 
 void
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0006-nss-Documentation.patch (35.3K, ../[email protected]/7-v44-0006-nss-Documentation.patch)
  download | inline diff:
From 3fd458bd5c716ef249ebfb4354ff5594714ada75 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v44 06/10] nss: Documentation

Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
 doc/src/sgml/acronyms.sgml     |  33 ++++
 doc/src/sgml/config.sgml       |  30 ++-
 doc/src/sgml/installation.sgml |  31 ++-
 doc/src/sgml/libpq.sgml        | 341 +++++++++++++++++++++++++--------
 doc/src/sgml/runtime.sgml      | 124 +++++++++++-
 src/backend/libpq/README.SSL   |  28 +++
 6 files changed, 494 insertions(+), 93 deletions(-)

diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><acronym>NSPR</acronym></term>
+    <listitem>
+     <para>
+      <ulink
+      url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+      Netscape Portable Runtime</ulink>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><acronym>NSS</acronym></term>
+    <listitem>
+     <para>
+      <ulink
+      url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+      Network Security Services</ulink>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><acronym>ODBC</acronym></term>
     <listitem>
@@ -550,6 +572,17 @@
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><acronym>PKCS#12</acronym></term>
+    <listitem>
+     <para>
+      <ulink
+      url="https://en.wikipedia.org/wiki/PKCS_12">
+      Public-Key Cryptography Standards #12</ulink>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><acronym>PL</acronym></term>
     <listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0a8e35c59f..fd1ba4d979 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+      <term><varname>ssl_database</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>ssl_database</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies the name of the file containing the server certificates and
+        keys when using <productname>NSS</productname> for
+        <acronym>SSL</acronym>/<acronym>TLS</acronym>
+        connections. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command
+        line.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
       <term><varname>ssl_ciphers</varname> (<type>string</type>)
       <indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
         connections using TLS version 1.2 and lower are affected.  There is
         currently no setting that controls the cipher choices used by TLS
         version 1.3 connections.  The default value is
-        <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>.  The default is usually a
+        <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+        been built with <productname>OpenSSL</productname> as the
+        <acronym>SSL</acronym> library.  The default is usually a
         reasonable choice unless you have specific security requirements.
        </para>
 
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
        <para>
         Sets an external command to be invoked when a passphrase for
         decrypting an SSL file such as a private key needs to be obtained.  By
-        default, this parameter is empty, which means the built-in prompting
-        mechanism is used.
+        default, this parameter is empty. When the server is using
+        <productname>OpenSSL</productname>, this means the built-in prompting
+        mechanism is used. When using <productname>NSS</productname>, there is
+        no default prompting so a blank callback will be used returning an
+        empty password. This requires that the certificate database hasn't
+        been created with a password.
        </para>
        <para>
         The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
        <listitem>
         <para>
          Build with support for <acronym>SSL</acronym> (encrypted)
-         connections. The only <replaceable>LIBRARY</replaceable>
-         supported is <option>openssl</option>. This requires the
-         <productname>OpenSSL</productname> package to be installed.
-         <filename>configure</filename> will check for the required
-         header files and libraries to make sure that your
-         <productname>OpenSSL</productname> installation is sufficient
-         before proceeding.
+         connections. <replaceable>LIBRARY</replaceable> must be one of:
         </para>
+        <itemizedlist>
+         <listitem>
+          <para>
+           <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+            This requires the <productname>OpenSSL</productname>
+            package to be installed.  <filename>configure</filename> will check
+            for the required header files and libraries to make sure
+            your <productname>OpenSSL</productname> installation is sufficient
+            before proceeding.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <option>nss</option> to build with <productname>libnss</productname> support.
+            This requires the <productname>NSS</productname> package to be installed. Additionally,
+            <productname>NSS</productname> requires <productname>NSPR</productname>
+            to be installed. <filename>configure</filename> will check for the
+            required header files and libraries to make sure that your
+            <productname>NSS</productname> installation is sufficient before
+            proceeding.
+          </para>
+         </listitem>
+        </itemizedlist>
        </listitem>
       </varlistentry>
 
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
        <function>longjmp(...)</function>, etc. It must return normally.
       </para>
 
+      <para>
+        <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+        the server was compiled with <productname>OpenSSL</productname>
+        support.
+      </para>
+
      </listitem>
     </varlistentry>
 
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
 </synopsis>
       </para>
 
+      <para>
+        <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+        the server was compiled with <productname>OpenSSL</productname>
+        support.
+      </para>
+
+     </listitem>
+    </varlistentry>
+
+    <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+     <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+     <listitem>
+      <para>
+       <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+       <application>libpq</application>'s default handling of password protected
+       objects in the <application>NSS</application> database using
+       <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+       The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+       which <application>libpq</application> will call
+       <emphasis>instead of</emphasis> its default
+       <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+       callback should determine the password for the token and return a
+       pointer to it. The returned pointer should be allocated with the NSS
+       <function>PORT_Strdup</function> function. The token for which the
+       password is requested is recorded in the slot anc can be identified by
+       calling <function>PK11_GetTokenName</function>. If no password is
+       known, the callback should return <literal>NULL</literal>. The memory
+       will be owned by <productname>NSS</productname> and should not be
+       freed.
+      </para>
+
+      <para>
+        <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+        the server was compiled with <productname>NSS</productname> support.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+     <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+     <listitem>
+      <para>
+       <function>PQgetSSLKeyPassHook_nss</function> returns the current
+       <productname>NSS</productname> password hook, or <literal>NULL</literal>
+       if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+      </para>
+
+      <para>
+        <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+        server was compiled with <productname>NSS</productname> support.
+      </para>
+
      </listitem>
     </varlistentry>
 
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         If set to 1, data sent over SSL connections will be compressed.  If
         set to 0, compression will be disabled.  The default is 0.  This
         parameter is ignored if a connection without SSL is made.
-       </para>
-
-       <para>
         SSL compression is nowadays considered insecure and its use is no
-        longer recommended.  <productname>OpenSSL</productname> 1.1.0 disables
-        compression by default, and many operating system distributions
-        disable it in prior versions as well, so setting this parameter to on
-        will not have any effect if the server does not accept compression.
-        <productname>PostgreSQL</productname> 14 disables compression
-        completely in the backend.
-       </para>
+        longer recommended.
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: 1.1.0 disables compression by
+           default, and many operating system distributions disable it in prior
+           versions as well, so setting this parameter to on will not have any
+           effect if the server does not accept compression.
+          </para>
+         </listitem>
 
-       <para>
-        If security is not a primary concern, compression can improve
-        throughput if the network is the bottleneck.  Disabling compression
-        can improve response time and throughput if CPU performance is the
-        limiting factor.
+         <listitem>
+          <para>
+           <productname>NSS</productname>: SSL compression is not supported in
+           <productname>NSS</productname>, this parameter has no effect.
+          </para>
+         </listitem>
+        </itemizedlist>
        </para>
       </listitem>
      </varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>sslcert</literal></term>
       <listitem>
        <para>
-        This parameter specifies the file name of the client SSL
-        certificate, replacing the default
-        <filename>~/.postgresql/postgresql.crt</filename>.
-        This parameter is ignored if an SSL connection is not made.
+        This parameter specifies the name or location of the client SSL
+        certificate.  This parameter is ignored if an SSL connection is not
+        made.
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: can replace the default
+           <filename>~/.postgresql/postgresql.crt</filename>.
+          </para>
+         </listitem>
+
+         <listitem>
+          <para>
+           <productname>NSS</productname>: the nickname of the client SSL
+           certificate.
+          </para>
+         </listitem>
+        </itemizedlist>
        </para>
       </listitem>
      </varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>sslkey</literal></term>
       <listitem>
        <para>
-        This parameter specifies the location for the secret key used for
-        the client certificate. It can either specify a file name that will
-        be used instead of the default
-        <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
-        obtained from an external <quote>engine</quote> (engines are
-        <productname>OpenSSL</productname> loadable modules).  An external engine
-        specification should consist of a colon-separated engine name and
-        an engine-specific key identifier.  This parameter is ignored if an
-        SSL connection is not made.
+        This parameter specifies the name or location for the secret key used
+        for the client certificate. This parameter is ignored if an SSL
+        connection is not made.
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: can either specify a file name
+           that will be used instead of the default
+           <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+           a key obtained from an external <quote>engine</quote> (engines are
+           <productname>OpenSSL</productname> loadable modules).  An external
+           engine specification should consist of a colon-separated engine name
+           and an engine-specific key identifier.
+          </para>
+         </listitem>
+
+         <listitem>
+          <para>
+           <productname>NSS</productname>: this parameter has no effect.  Keys
+           should be loaded into the <productname>NSS</productname>
+           database.
+          </para>
+         </listitem>
+        </itemizedlist>
        </para>
       </listitem>
      </varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
        <para>
         This parameter specifies the password for the secret key specified in
         <literal>sslkey</literal>, allowing client certificate private keys
-        to be stored in encrypted form on disk even when interactive passphrase
+        to be stored in encrypted form even when interactive passphrase
         input is not practical.
-       </para>
-       <para>
-        Specifying this parameter with any non-empty value suppresses the
-        <literal>Enter PEM pass phrase:</literal>
-        prompt that <productname>OpenSSL</productname> will emit by default
-        when an encrypted client certificate key is provided to
-        <literal>libpq</literal>.
-       </para>
-       <para>
-        If the key is not encrypted this parameter is ignored. The parameter
-        has no effect on keys specified by <productname>OpenSSL</productname>
-        engines unless the engine uses the <productname>OpenSSL</productname>
-        password callback mechanism for prompts.
-       </para>
-       <para>
-        There is no environment variable equivalent to this option, and no
-        facility for looking it up in <filename>.pgpass</filename>. It can be
-        used in a service file connection definition. Users with
-        more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
-        tools like PKCS#11 or USB crypto offload devices.
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: specifying this parameter with
+           any non-empty value suppresses the
+           <literal>Enter PEM pass phrase:</literal> prompt that
+           <productname>OpenSSL</productname> will emit by default when an
+           encrypted client certificate key is provided to
+           <literal>libpq</literal>.
+          </para>
+          <para>
+           If the key is not encrypted this parameter is ignored. The parameter
+           has no effect on keys specified by <productname>OpenSSL</productname>
+           engines unless the engine uses the <productname>OpenSSL</productname>
+           password callback mechanism for prompts.
+          </para>
+          <para>
+           There is no environment variable equivalent to this option, and no
+           facility for looking it up in <filename>.pgpass</filename>. It can be
+           used in a service file connection definition. Users with
+           more sophisticated uses should consider using
+           <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+           USB crypto offload devices.
+          </para>
+         </listitem>
+
+         <listitem>
+          <para>
+           <productname>NSS</productname>: specifying the parameter is required
+           in case any password protected items are referenced in the
+           <productname>NSS</productname> database, or if the database itself
+           is password protected. All attempts to decrypt objects which are
+           password protected in the database will use this password.
+          </para>
+         </listitem>
+        </itemizedlist>
        </para>
       </listitem>
      </varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>sslrootcert</literal></term>
       <listitem>
        <para>
-        This parameter specifies the name of a file containing SSL
-        certificate authority (<acronym>CA</acronym>) certificate(s).
-        If the file exists, the server's certificate will be verified
-        to be signed by one of these authorities.  The default is
-        <filename>~/.postgresql/root.crt</filename>.
+        This parameter specifies the name of SSL certificate authority
+        (<acronym>CA</acronym>) certificate(s).
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: specifies the name of a file
+           containing SSL certificate authority (<acronym>CA</acronym>)
+           certificate(s).  If the file exists, the server's certificate will
+           be verified to be signed by one of these authorities.  The default is
+           <filename>~/.postgresql/root.crt</filename>.
+          </para>
+         </listitem>
+
+         <listitem>
+          <para>
+           <productname>NSS</productname>: this parameter has no effect.  CA
+           certificates should be loaded into the <productname>NSS</productname>
+           database using the <application>certutil</application> tool.
+          </para>
+         </listitem>
+        </itemizedlist>
        </para>
       </listitem>
      </varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <listitem>
        <para>
         This parameter specifies the file name of the SSL certificate
-        revocation list (CRL).  Certificates listed in this file, if it
-        exists, will be rejected while attempting to authenticate the
-        server's certificate.  If neither
-        <xref linkend='libpq-connect-sslcrl'/> nor
-        <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
-        taken as
-        <filename>~/.postgresql/root.crl</filename>.
+        revocation list (CRL).  
+
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: Certificates listed in this file,
+           if it exists, will be rejected while attempting to authenticate the
+           server's certificate.  If neither
+           <xref linkend='libpq-connect-sslcrl'/> nor
+           <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+           taken as <filename>~/.postgresql/root.crl</filename>. 
+          </para>
+         </listitem>
+
+         <listitem>
+          <para>
+           <productname>NSS</productname>: This parameter has no effect.  CRL
+           files should be loaded into the <productname>NSS</productname>
+           database using the <application>crlutil</application> tool.
+          </para>
+         </listitem>
+        </itemizedlist>
+
        </para>
       </listitem>
      </varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         revocation list (CRL).  Certificates listed in the files in this
         directory, if it exists, will be rejected while attempting to
         authenticate the server's certificate.
-       </para>
 
-       <para>
-        The directory needs to be prepared with the
-        <productname>OpenSSL</productname> command
-        <literal>openssl rehash</literal> or <literal>c_rehash</literal>.  See
-        its documentation for details.
-       </para>
+        <itemizedlist>
+         <listitem>
+          <para>
+           <productname>OpenSSL</productname>: the directory needs to be
+           prepared with the <productname>OpenSSL</productname> command
+           <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+           See its documentation for details.
+          </para>
+          <para>
+           Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+           be specified together.
+          </para>
+         </listitem>
 
-       <para>
-        Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
-        specified together.
+         <listitem>
+          <para>
+           <productname>NSS</productname>: This parameter has no effect.  CRL
+           files should be loaded into the <productname>NSS</productname>
+           database using the <application>crlutil</application> tool.
+          </para>
+         </listitem>
+        </itemizedlist>
        </para>
+
       </listitem>
      </varlistentry>
 
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         for the connection. Valid values are <literal>TLSv1</literal>,
         <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
         <literal>TLSv1.3</literal>. The supported protocols depend on the
-        version of <productname>OpenSSL</productname> used, older versions
-        not supporting the most modern protocol versions. If not specified,
+        version of the SSL library used, older versions may 
+        not support the most modern protocol versions. If not specified,
         the default is <literal>TLSv1.2</literal>, which satisfies industry
         best practices as of this writing.
        </para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         for the connection. Valid values are <literal>TLSv1</literal>,
         <literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
         <literal>TLSv1.3</literal>. The supported protocols depend on the
-        version of <productname>OpenSSL</productname> used, older versions
-        not supporting the most modern protocol versions. If not set, this
+        version of the SSL library used, older versions
+        may not support the most modern protocol versions. If not set, this
         parameter is ignored and the connection will use the maximum bound
         defined by the backend, if set. Setting the maximum protocol version
         is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
       </para>
       <para>
        The struct(s) available depend on the SSL implementation in use.
+      </para>
+      <para>
        For <productname>OpenSSL</productname>, there is one struct,
        available under the name "OpenSSL", and it returns a pointer to the
        <productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
 ]]></programlisting>
       </para>
       <para>
-       This structure can be used to verify encryption levels, check server
-       certificates, and more. Refer to the <productname>OpenSSL</productname>
-       documentation for information about this structure.
+       For <productname>NSS</productname>, there is one struct available under
+       the name "NSS", and it returns a pointer to the
+       <productname>NSS</productname> <acronym>SSL</acronym>
+       <literal>PRFileDesc</literal> associated with the connection.
+      </para>
+      <para>
+       These structures can be used to verify encryption levels, check server
+       certificates, and more. Refer to the <acronym>SSL</acronym> library
+       documentation for information about these structures.
       </para>
      </listitem>
     </varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
        <xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
        connection, use <xref linkend="libpq-PQsslAttribute"/>.
       </para>
+      <para>
+       This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+       librariaes other than <productname>OpenSSL</productname> are used.
+      </para>
      </listitem>
     </varlistentry>
 
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
        before first opening a database connection.  Also be sure that you
        have done that initialization before opening a database connection.
       </para>
+
+      <para>
+       This function does nothing when using <productname>NSS</productname> as
+       the <acronym>SSL</acronym> library, no special initialization is
+       required for <productname>NSS</productname>.
+      </para>
      </listitem>
     </varlistentry>
 
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
        might be preferable for applications that need to work with older
        versions of <application>libpq</application>.
       </para>
+
+      <para>
+       This function does nothing when using <productname>NSS</productname> as
+       the <acronym>SSL</acronym> library, no special initialization is
+       required for <productname>NSS</productname>.
+      </para>
      </listitem>
     </varlistentry>
    </variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d74d1ed7af..6318daa07f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
 
   <indexterm zone="ssl-tcp">
    <primary>SSL</primary>
+   <secondary>TLS</secondary>
   </indexterm>
 
   <para>
    <productname>PostgreSQL</productname> has native support for using
    <acronym>SSL</acronym> connections to encrypt client/server communications
    for increased security. This requires that
-   <productname>OpenSSL</productname> is installed on both client and
+   a supported TLS library is installed on both client and
    server systems and that support in <productname>PostgreSQL</productname> is
    enabled at build time (see <xref linkend="installation"/>).
+   Supported libraries are <productname>OpenSSL</productname> and
+   <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+   <acronym>TLS</acronym> are often used interchangeably to mean a secure
+   connection using a <acronym>TLS</acronym> protocol, even though
+   <acronym>SSL</acronym> protocols are no longer supported.
   </para>
 
   <sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   </para>
 
   <para>
-   To start in <acronym>SSL</acronym> mode, files containing the server certificate
-   and private key must exist.  By default, these files are expected to be
+   To start in <acronym>SSL</acronym> mode, a server certificate
+   and private key must exist. The below sections on the different libraries
+   will discuss how to configure these.
+  </para>
+   
+  <para>
+   By default, these files are expected to be
    named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
    the server's data directory, but other names and locations can be specified
    using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   </note>
   </sect2>
 
+  <sect2 id="ssl-nss-config">
+   <title>NSS Configuration</title>
+
+  <para>
+   <productname>PostgreSQL</productname> will look for certificates and keys
+   in the <productname>NSS</productname> database specified by the parameter
+   <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+   The parameters for certificate and key filenames are used to identify the
+   nicknames in the database.
+  </para>
+  </sect2>
+
   <sect2 id="ssl-client-certificates">
    <title>Using Client Certificates</title>
 
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   </sect2>
 
   <sect2 id="ssl-server-files">
-   <title>SSL Server File Usage</title>
+   <title>SSL Server File Parameter Usage</title>
 
    <para>
     <xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
       <entry>client certificate must not be on this list</entry>
      </row>
 
+     <row>
+      <entry><xref linkend="guc-ssl-database"/></entry>
+      <entry>certificate database</entry>
+      <entry>contains server certificates, keys and revocation lists; only
+      used when <productname>PostgreSQL</productname> is built with support
+      for <productname>NSS</productname>.</entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   </sect2>
 
   <sect2 id="ssl-certificate-creation">
-   <title>Creating Certificates</title>
+   <title>Creating Certificates with OpenSSL</title>
 
    <para>
      To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
    </para>
   </sect2>
 
+  <sect2 id="nss-certificate-database">
+   <title>NSS Certificate Databases</title>
+
+   <para>
+    When using <productname>NSS</productname>, all certificates and keys must
+    be loaded into an <productname>NSS</productname> certificate database.
+   </para>
+
+   <para>
+    To create a new <productname>NSS</productname> certificate database and
+    load the certificates created in <xref linkend="ssl-certificate-creation" />,
+    use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+    This will give the certificate the filename as the nickname identifier in
+    the database which is created as <filename>server.db</filename>.
+   </para>
+   <para>
+    Then load the server key, which requires converting it to
+    <acronym>PKCS#12</acronym> format using the
+    <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+  -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+   </para>
+   <para>
+    Finally a certificate revocation list can be loaded with the following
+    commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+   </para>
+  </sect2>
+
+  <sect2 id="nss-ssl-certificate-creation">
+   <title>Creating Certificates with NSS</title>
+
+   <para>
+    To create a simple self-signed CA and certificate for the server, use the
+    following NSS commands. Replace
+    <replaceable>*.yourdomain.com</replaceable> with the server's host
+    name. Remove <replaceable>--empty-password</replaceable> in order to set
+    a password protecting the databases. The password can be passed to
+    <application>certutil</application> with the <literal>-f</literal> parameter.
+    First create a self-signed CA. To use a different certificate validity
+    time than the default, use <replaceable>-v</replaceable> to specify the
+    number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+  -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+  --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+  -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+    Now create a server certificate database, and create a certificate signing
+    request for the CA. Then sign the request with the already created CA and
+    insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+  -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+  -o server_cert.der -m <replaceable>5433</replaceable> \
+  --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+  --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+    The server certificate is loaded into the certificate database as well as
+    the CA certificate in order to provide the chain of certificates.
+   </para>
+  </sect2>
+
  </sect1>
 
  <sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
 The downside to EDH is that it makes it impossible to use ssldump(1)
 if there's a problem establishing an SSL session.  In this case you'll
 need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0007-nss-Support-NSS-in-pgcrypto.patch (24.9K, ../[email protected]/8-v44-0007-nss-Support-NSS-in-pgcrypto.patch)
  download | inline diff:
From 9abf0fc21cac45603caa0d3d488fad73194eca02 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v44 07/10] nss: Support NSS in pgcrypto

This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
 contrib/pgcrypto/Makefile  |   7 +-
 contrib/pgcrypto/nss.c     | 753 +++++++++++++++++++++++++++++++++++++
 doc/src/sgml/pgcrypto.sgml |  49 ++-
 3 files changed, 795 insertions(+), 14 deletions(-)
 create mode 100644 contrib/pgcrypto/nss.c

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
 OSSL_SRCS = openssl.c pgp-mpi-openssl.c
 OSSL_TESTS = sha2 des 3des cast5
 
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
 ZLIB_TST = pgp-compression
 ZLIB_OFF_TST = pgp-zlib-DISABLED
 
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
 CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
 
 SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ *	  Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC	(1)
+#define BLOWFISH_ECB	(2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+	NSSInitContext *context;
+	PK11Context *hash_context;
+	HASH_HashType hash_type;
+}			nss_digest;
+
+typedef struct cipher_implementation
+{
+	/* Function pointers to cipher operations */
+	int			(*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+	unsigned	(*get_block_size) (PX_Cipher *pxc);
+	unsigned	(*get_key_size) (PX_Cipher *pxc);
+	unsigned	(*get_iv_size) (PX_Cipher *pxc);
+	int			(*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+	int			(*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+	void		(*free) (PX_Cipher *pxc);
+
+	/* The mechanism describing the cipher used */
+	union
+	{
+		CK_MECHANISM_TYPE nss;
+		CK_ULONG	internal;
+	}			mechanism;
+	int			keylen;
+	bool		is_nss;
+}			cipher_implementation;
+
+typedef struct nss_cipher
+{
+	const cipher_implementation *impl;
+	NSSInitContext *context;
+	PK11Context *crypt_context;
+	SECItem    *params;
+
+	PK11SymKey *encrypt_key;
+	PK11SymKey *decrypt_key;
+}			nss_cipher;
+
+typedef struct internal_cipher
+{
+	const cipher_implementation *impl;
+	BlowfishContext context;
+}			internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+	const char *name;
+	const cipher_implementation *impl;
+}			nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+	if (type == HASH_AlgMD2)
+		return SEC_OID_MD2;
+	if (type == HASH_AlgMD5)
+		return SEC_OID_MD5;
+	if (type == HASH_AlgSHA1)
+		return SEC_OID_SHA1;
+	if (type == HASH_AlgSHA224)
+		return SEC_OID_SHA224;
+	if (type == HASH_AlgSHA256)
+		return SEC_OID_SHA256;
+	if (type == HASH_AlgSHA384)
+		return SEC_OID_SHA384;
+	if (type == HASH_AlgSHA512)
+		return SEC_OID_SHA512;
+
+	return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+	nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+	const SECHashObject *object;
+
+	object = HASH_GetHashObject(digest->hash_type);
+	return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+	nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+	const SECHashObject *object;
+
+	object = HASH_GetHashObject(digest->hash_type);
+	return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+	nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+	PRBool		free_ctx = PR_TRUE;
+
+	PK11_DestroyContext(digest->hash_context, free_ctx);
+	NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+	nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+	PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+	nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+	PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+	unsigned int outlen;
+	nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+	const SECHashObject *object;
+
+	object = HASH_GetHashObject(digest->hash_type);
+	PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+	PX_MD	   *pxmd;
+	NSSInitParameters params;
+	NSSInitContext *nss_context;
+	bool		found = false;
+	nss_digest *digest;
+	SECStatus	status;
+	SECOidData *hash;
+	HASH_HashType t;
+
+	/*
+	 * Initialize our own NSS context without a database backing it.
+	 */
+	memset(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+	nss_context = NSS_InitContext("", "", "", "", &params,
+								  NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+								  NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+								  NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+	/*
+	 * There is no API function for looking up a digest algorithm from a name
+	 * string, but there is a publicly accessible array which can be scanned
+	 * for the name.
+	 */
+	for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+	{
+		SECOidTag	hash_oid;
+		char	   *p;
+
+		hash_oid = nss_GetHashOidTagByHashType(t);
+
+		if (hash_oid == SEC_OID_UNKNOWN)
+			return PXE_NO_HASH;
+
+		hash = SECOID_FindOIDByTag(hash_oid);
+		if (pg_strcasecmp(hash->desc, name) == 0)
+		{
+			found = true;
+			break;
+		}
+
+		/*
+		 * NSS saves the algorithm names using SHA-xxx notation whereas
+		 * OpenSSL use SHAxxx. To make sure the user finds the requested
+		 * algorithm let's remove the dash and compare that spelling as well.
+		 */
+		if ((p = strchr(hash->desc, '-')) != NULL)
+		{
+			char		tmp[12];
+
+			memcpy(tmp, hash->desc, p - hash->desc);
+			memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+			if (pg_strcasecmp(tmp, name) == 0)
+			{
+				found = true;
+				break;
+			}
+		}
+	}
+
+	if (!found)
+		return PXE_NO_HASH;
+
+	digest = palloc(sizeof(*digest));
+
+	digest->context = nss_context;
+	digest->hash_context = PK11_CreateDigestContext(hash->offset);
+	digest->hash_type = t;
+	if (digest->hash_context == NULL)
+	{
+		pfree(digest);
+		return -1;
+	}
+
+	status = PK11_DigestBegin(digest->hash_context);
+	if (status != SECSuccess)
+	{
+		PK11_DestroyContext(digest->hash_context, PR_TRUE);
+		pfree(digest);
+		return -1;
+	}
+
+	pxmd = palloc(sizeof(*pxmd));
+	pxmd->result_size = nss_digest_result_size;
+	pxmd->block_size = nss_digest_block_size;
+	pxmd->reset = nss_digest_reset;
+	pxmd->update = nss_digest_update;
+	pxmd->finish = nss_digest_finish;
+	pxmd->free = nss_digest_free;
+	pxmd->p.ptr = (void *) digest;
+
+	*res = pxmd;
+
+	return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+	internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+	blowfish_setkey(&cipher->context, key, klen);
+	if (iv)
+		blowfish_setiv(&cipher->context, iv);
+
+	return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+	internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+	uint8	   *buf;
+
+	if (dlen == 0)
+		return 0;
+
+	if (dlen & 7)
+		return PXE_NOTBLOCKSIZE;
+
+	buf = palloc(dlen);
+	memcpy(buf, data, dlen);
+
+	if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+		blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+	else
+		blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+	memcpy(res, buf, dlen);
+	pfree(buf);
+
+	return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+	internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+	if (dlen == 0)
+		return 0;
+
+	if (dlen & 7)
+		return PXE_NOTBLOCKSIZE;
+
+	memcpy(res, data, dlen);
+	if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+		blowfish_decrypt_ecb(res, dlen, &cipher->context);
+	else
+		blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+	return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+	internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+	pfree(cipher);
+	pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+							const uint8 *iv)
+{
+	nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+	SECItem		iv_item;
+	unsigned char *iv_item_data;
+	SECItem		key_item;
+	int			keylen;
+	PK11SlotInfo *slot;
+
+	if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+		cipher->impl->mechanism.nss == CKM_AES_ECB)
+	{
+		if (klen <= 128 / 8)
+			keylen = 128 / 8;
+		else if (klen <= 192 / 8)
+			keylen = 192 / 8;
+		else if (klen <= 256 / 8)
+			keylen = 256 / 8;
+		else
+			return PXE_CIPHER_INIT;
+	}
+	else
+		keylen = cipher->impl->keylen;
+
+	key_item.type = siBuffer;
+	key_item.data = (unsigned char *) key;
+	key_item.len = keylen;
+
+	/*
+	 * If hardware acceleration is configured in NSS one can theoretically get
+	 * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+	 * to it. Unless there are complaints, using the simpler API will make
+	 * error handling easier though.
+	 */
+	slot = PK11_GetInternalSlot();
+	if (!slot)
+		return PXE_CIPHER_INIT;
+
+	/*
+	 * The key must be set up for the operation, and since we don't know at
+	 * this point whether we are asked to encrypt or decrypt we need to store
+	 * both versions.
+	 */
+	cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+											PK11_OriginUnwrap,
+											CKA_DECRYPT, &key_item,
+											NULL);
+	cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+											PK11_OriginUnwrap,
+											CKA_ENCRYPT, &key_item,
+											NULL);
+	PK11_FreeSlot(slot);
+
+	if (!cipher->decrypt_key || !cipher->encrypt_key)
+		return PXE_CIPHER_INIT;
+
+	if (iv)
+	{
+		iv_item.type = siBuffer;
+		iv_item.data = (unsigned char *) iv;
+		iv_item.len = nss_get_iv_size(pxc);
+	}
+	else
+	{
+		/*
+		 * The documentation states that either passing .data = 0; .len = 0;
+		 * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+		 * done when IV is missing. That however leads to segfaults in the
+		 * library, the workaround that works in modern  library versions is
+		 * to pass in a keysized zeroed out IV.
+		 */
+		iv_item_data = palloc0(nss_get_iv_size(pxc));
+		iv_item.type = siBuffer;
+		iv_item.data = iv_item_data;
+		iv_item.len = nss_get_iv_size(pxc);
+	}
+
+	cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+	/* If we had to make a mock IV, free it once made into a param */
+	if (!iv)
+		pfree(iv_item_data);
+
+	if (cipher->params == NULL)
+		return PXE_CIPHER_INIT;
+
+	return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+	nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+	SECStatus	status;
+	int			outlen;
+
+	if (!cipher->crypt_context)
+	{
+		cipher->crypt_context =
+			PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+									   cipher->decrypt_key, cipher->params);
+	}
+
+	status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+	if (status != SECSuccess)
+		return PXE_DECRYPT_FAILED;
+
+	return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+	nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+	SECStatus	status;
+	int			outlen;
+
+	if (!cipher->crypt_context)
+	{
+		cipher->crypt_context =
+			PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+									   cipher->decrypt_key, cipher->params);
+	}
+
+	status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+	if (status != SECSuccess)
+		return PXE_DECRYPT_FAILED;
+
+	return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+	nss_cipher *cipher = pxc->ptr;
+	PRBool		free_ctx = PR_TRUE;
+
+	PK11_FreeSymKey(cipher->encrypt_key);
+	PK11_FreeSymKey(cipher->decrypt_key);
+	PK11_DestroyContext(cipher->crypt_context, free_ctx);
+	NSS_ShutdownContext(cipher->context);
+	pfree(cipher);
+
+	pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+	nss_cipher *cipher = pxc->ptr;
+
+	return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+	nss_cipher *cipher = pxc->ptr;
+
+	return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+	nss_cipher *cipher = pxc->ptr;
+
+	return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+	return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+	return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+	return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+	.init = nss_symkey_blockcipher_init,
+	.get_block_size = nss_get_block_size,
+	.get_key_size = nss_get_key_size,
+	.get_iv_size = nss_get_iv_size,
+	.encrypt = nss_encrypt,
+	.decrypt = nss_decrypt,
+	.free = nss_free,
+	.mechanism.nss = CKM_DES_CBC,
+	.keylen = 8,
+	.is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+	.init = nss_symkey_blockcipher_init,
+	.get_block_size = nss_get_block_size,
+	.get_key_size = nss_get_key_size,
+	.get_iv_size = nss_get_iv_size,
+	.encrypt = nss_encrypt,
+	.decrypt = nss_decrypt,
+	.free = nss_free,
+	.mechanism.nss = CKM_DES_ECB,
+	.keylen = 8,
+	.is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+	.init = nss_symkey_blockcipher_init,
+	.get_block_size = nss_get_block_size,
+	.get_key_size = nss_get_key_size,
+	.get_iv_size = nss_get_iv_size,
+	.encrypt = nss_encrypt,
+	.decrypt = nss_decrypt,
+	.free = nss_free,
+	.mechanism.nss = CKM_DES3_CBC,
+	.keylen = 24,
+	.is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+	.init = nss_symkey_blockcipher_init,
+	.get_block_size = nss_get_block_size,
+	.get_key_size = nss_get_key_size,
+	.get_iv_size = nss_get_iv_size,
+	.encrypt = nss_encrypt,
+	.decrypt = nss_decrypt,
+	.free = nss_free,
+	.mechanism.nss = CKM_DES3_ECB,
+	.keylen = 24,
+	.is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+	.init = nss_symkey_blockcipher_init,
+	.get_block_size = nss_get_block_size,
+	.get_key_size = nss_get_key_size,
+	.get_iv_size = nss_get_iv_size,
+	.encrypt = nss_encrypt,
+	.decrypt = nss_decrypt,
+	.free = nss_free,
+	.mechanism.nss = CKM_AES_CBC,
+	.keylen = 32,
+	.is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+	.init = nss_symkey_blockcipher_init,
+	.get_block_size = nss_get_block_size,
+	.get_key_size = nss_get_key_size,
+	.get_iv_size = nss_get_iv_size,
+	.encrypt = nss_encrypt,
+	.decrypt = nss_decrypt,
+	.free = nss_free,
+	.mechanism.nss = CKM_AES_ECB,
+	.keylen = 32,
+	.is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+	.init = bf_init,
+	.get_block_size = bf_get_block_size,
+	.get_key_size = bf_get_key_size,
+	.get_iv_size = bf_get_iv_size,
+	.encrypt = bf_encrypt,
+	.decrypt = bf_decrypt,
+	.free = bf_free,
+	.mechanism.internal = BLOWFISH_ECB,
+	.keylen = 56,
+	.is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+	.init = bf_init,
+	.get_block_size = bf_get_block_size,
+	.get_key_size = bf_get_key_size,
+	.get_iv_size = bf_get_iv_size,
+	.encrypt = bf_encrypt,
+	.decrypt = bf_decrypt,
+	.free = bf_free,
+	.mechanism.internal = BLOWFISH_CBC,
+	.keylen = 56,
+	.is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+	{"des", &nss_des_cbc},
+	{"des-cbc", &nss_des_cbc},
+	{"des-ecb", &nss_des_ecb},
+	{"des3-cbc", &nss_des3_cbc},
+	{"3des", &nss_des3_cbc},
+	{"3des-cbc", &nss_des3_cbc},
+	{"des3-ecb", &nss_des3_ecb},
+	{"3des-ecb", &nss_des3_ecb},
+	{"aes", &nss_aes_cbc},
+	{"aes-cbc", &nss_aes_cbc},
+	{"aes-ecb", &nss_aes_ecb},
+	{"rijndael", &nss_aes_cbc},
+	{"rijndael-cbc", &nss_aes_cbc},
+	{"rijndael-ecb", &nss_aes_ecb},
+	{"blowfish", &nss_bf_cbc},
+	{"blowfish-cbc", &nss_bf_cbc},
+	{"blowfish-ecb", &nss_bf_ecb},
+	{"bf", &nss_bf_cbc},
+	{"bf-cbc", &nss_bf_cbc},
+	{"bf-ecb", &nss_bf_ecb},
+	{NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+	const nss_cipher_ref *cipher_ref;
+	PX_Cipher  *px_cipher;
+	NSSInitParameters params;
+	NSSInitContext *nss_context;
+	nss_cipher *cipher;
+	internal_cipher *int_cipher;
+
+	for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+	{
+		if (strcmp(cipher_ref->name, alias) == 0)
+			break;
+	}
+
+	if (!cipher_ref->name)
+		return PXE_NO_CIPHER;
+
+	/*
+	 * Fill in the PX_Cipher to pass back to PX describing the operations to
+	 * perform in order to use the cipher.
+	 */
+	px_cipher = palloc(sizeof(*px_cipher));
+	px_cipher->block_size = cipher_ref->impl->get_block_size;
+	px_cipher->key_size = cipher_ref->impl->get_key_size;
+	px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+	px_cipher->free = cipher_ref->impl->free;
+	px_cipher->init = cipher_ref->impl->init;
+	px_cipher->encrypt = cipher_ref->impl->encrypt;
+	px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+	/*
+	 * Set the private data, which is different for NSS and internal ciphers
+	 */
+	if (cipher_ref->impl->is_nss)
+	{
+		/*
+		 * Initialize our own NSS context without a database backing it. At
+		 * some point we might want to allow users to use stored keys in the
+		 * database rather than passing them via SELECT encrypt(), and then
+		 * this need to be changed to open a user specified database.
+		 */
+		memset(&params, 0, sizeof(params));
+		params.length = sizeof(params);
+		nss_context = NSS_InitContext("", "", "", "", &params,
+									  NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+									  NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+									  NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+		/* nss_cipher is the private state struct for the operation */
+		cipher = palloc(sizeof(*cipher));
+		cipher->context = nss_context;
+		cipher->impl = cipher_ref->impl;
+		cipher->crypt_context = NULL;
+
+		px_cipher->ptr = cipher;
+	}
+	else
+	{
+		int_cipher = palloc0(sizeof(*int_cipher));
+
+		int_cipher->impl = cipher_ref->impl;
+		px_cipher->ptr = int_cipher;
+	}
+
+	*res = px_cipher;
+	return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
     <literal>sha224</literal>, <literal>sha256</literal>,
     <literal>sha384</literal> and <literal>sha512</literal>.
     If <filename>pgcrypto</filename> was built with
-    <productname>OpenSSL</productname>, more algorithms are available, as
-    detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+    <productname>OpenSSL</productname> or <productname>NSS</productname>,
+    more algorithms are available, as
+    detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
    </para>
 
    <para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
     Which cipher algorithm to use.
    </para>
 <literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
 Default: aes128
 Applies to: pgp_sym_encrypt, pgp_pub_encrypt
 </literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
    <para>
     <filename>pgcrypto</filename> configures itself according to the findings of the
     main PostgreSQL <literal>configure</literal> script.  The options that
-    affect it are <literal>--with-zlib</literal> and
-    <literal>--with-ssl=openssl</literal>.
+    affect it are <literal>--with-zlib</literal>,
+    <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
    </para>
 
    <para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
     BIGNUM functions.
    </para>
 
-   <table id="pgcrypto-with-without-openssl">
-    <title>Summary of Functionality with and without OpenSSL</title>
-    <tgroup cols="3">
+   <table id="pgcrypto-with-without-openssl-nss">
+    <title>Summary of Functionality with and without external cryptographic
+      library</title>
+    <tgroup cols="4">
      <thead>
       <row>
        <entry>Functionality</entry>
        <entry>Built-in</entry>
        <entry>With OpenSSL</entry>
+       <entry>With NSS</entry>
       </row>
      </thead>
      <tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
        <entry>MD5</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>SHA1</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>SHA224/256/384/512</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>Other digest algorithms</entry>
        <entry>no</entry>
        <entry>yes (Note 1)</entry>
+       <entry>yes (Note 1)</entry>
       </row>
       <row>
        <entry>Blowfish</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes (Note 2)</entry>
       </row>
       <row>
        <entry>AES</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
-       <entry>DES/3DES/CAST5</entry>
+       <entry>DES/3DES</entry>
        <entry>no</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
+      </row>
+      <row>
+       <entry>CAST5</entry>
+       <entry>no</entry>
+       <entry>yes</entry>
+       <entry>no</entry>
       </row>
       <row>
        <entry>Raw encryption</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>PGP Symmetric encryption</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>PGP Public-Key encryption</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
      </tbody>
     </tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
    <orderedlist>
     <listitem>
      <para>
-      Any digest algorithm <productname>OpenSSL</productname> supports
-      is automatically picked up.
-      This is not possible with ciphers, which need to be supported
+      Any digest algorithm included with the library that
+      <productname>PostgreSQL</productname> is compiled with is automatically
+      picked up. This is not possible with ciphers, which need to be supported
       explicitly.
      </para>
     </listitem>
+    <listitem>
+     <para>
+      Blowfish is not supported by <productname>NSS</productname>, the
+      built-in implementation is used as a fallback.
+     </para>
+    </listitem>
    </orderedlist>
   </sect3>
 
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0008-nss-Support-NSS-in-sslinfo.patch (3.6K, ../[email protected]/9-v44-0008-nss-Support-NSS-in-sslinfo.patch)
  download | inline diff:
From 86ce7b7fc6c7e0ec5a2f91a8d007e2bfd345623f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v44 08/10] nss: Support NSS in sslinfo

Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
 contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
 doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
 
 #include "postgres.h"
 
+#ifdef USE_OPENSSL
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 #include <openssl/asn1.h>
+#endif
 
 #include "access/htup_details.h"
 #include "funcapi.h"
@@ -21,6 +23,7 @@
 
 PG_MODULE_MAGIC;
 
+#ifdef USE_OPENSSL
 static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
 static Datum ASN1_STRING_to_text(ASN1_STRING *str);
 
@@ -31,6 +34,7 @@ typedef struct
 {
 	TupleDesc	tupdesc;
 } SSLExtensionInfoContext;
+#endif
 
 /*
  * Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
 }
 
 
+#ifdef USE_OPENSSL
 /*
  * Converts OpenSSL ASN1_STRING structure into text
  *
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
 	else
 		return result;
 }
+#endif							/* USE_OPENSSL */
 
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_NULL();
+}
+#endif							/* USE_NSS */
 
 /*
  * Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
 }
 
 
+#ifdef USE_OPENSSL
 /*
  * Returns information about available SSL extensions.
  *
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 	/* All done */
 	SRF_RETURN_DONE(funcctx);
 }
+#endif							/* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_NULL();
+}
+#endif							/* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
 
  <para>
   This extension won't build at all unless the installation was
-  configured with <literal>--with-ssl=openssl</literal>.
+  configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+  or <literal>--with-ssl=nss</literal>.
  </para>
 
  <sect2>
@@ -208,6 +209,9 @@ emailAddress
      the X.500 and X.509 standards, so you cannot just assign arbitrary
      meaning to them.
     </para>
+    <para>
+     This function is only available when using <productname>OpenSSL</productname>.
+    </para>
     </listitem>
    </varlistentry>
 
@@ -223,6 +227,9 @@ emailAddress
      Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
      rather than the certificate subject.
     </para>
+    <para>
+     This function is only available when using <productname>OpenSSL</productname>.
+    </para>
     </listitem>
    </varlistentry>
 
@@ -238,6 +245,9 @@ emailAddress
      Provide information about extensions of client certificate: extension name,
      extension value, and if it is a critical extension.
     </para>
+    <para>
+     This function is only available when using <productname>OpenSSL</productname>.
+    </para>
     </listitem>
    </varlistentry>
   </variablelist>
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0009-nss-Support-NSS-in-cryptohash.patch (6.1K, ../[email protected]/10-v44-0009-nss-Support-NSS-in-cryptohash.patch)
  download | inline diff:
From 619abeb421370fee1e62c018a39a2396b8f17fd9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v44 09/10] nss: Support NSS in cryptohash

This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
 src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
 1 file changed, 264 insertions(+)
 create mode 100644 src/common/cryptohash_nss.c

diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..b90aed83f1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ *	  Set of wrapper routines on top of NSS to support cryptographic
+ *	  hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling.  In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+	pg_cryptohash_type type;
+
+	PK11Context *pk11_context;
+	SECOidTag	hash_type;
+
+#ifndef FRONTEND
+	ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+	NSSInitParameters params;
+	pg_cryptohash_ctx *ctx;
+	SECOidData *hash;
+	SECStatus status;
+
+#ifndef FRONTEND
+	/*
+	 * Make sure that the resource owner has space to remember this reference.
+	 * This can error out with "out of memory", so do this before any other
+	 * allocation to avoid leaking.
+	 */
+	ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+	ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+	if (!ctx)
+		goto error;
+
+	switch(type)
+	{
+		case PG_SHA1:
+			ctx->hash_type = SEC_OID_SHA1;
+			break;
+		case PG_MD5:
+			ctx->hash_type = SEC_OID_MD5;
+			break;
+		case PG_SHA224:
+			ctx->hash_type = SEC_OID_SHA224;
+			break;
+		case PG_SHA256:
+			ctx->hash_type = SEC_OID_SHA256;
+			break;
+		case PG_SHA384:
+			ctx->hash_type = SEC_OID_SHA384;
+			break;
+		case PG_SHA512:
+			ctx->hash_type = SEC_OID_SHA512;
+			break;
+	}
+
+	/*
+	 * Initialize our own NSS context without a database backing it.
+	 */
+	memset(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+	status = NSS_NoDB_Init(".");
+	if (status != SECSuccess)
+	{
+		explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+		FREE(ctx);
+		goto error;
+	}
+
+	ctx->type = type;
+	hash = SECOID_FindOIDByTag(ctx->hash_type);
+	ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+	if (!ctx->pk11_context)
+	{
+		explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+		FREE(ctx);
+		goto error;
+	}
+
+#ifndef FRONTEND
+	ctx->resowner = CurrentResourceOwner;
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+									PointerGetDatum(ctx));
+#endif
+
+	return ctx;
+
+error:
+#ifndef FRONTEND
+	ereport(ERROR,
+			(errcode(ERRCODE_OUT_OF_MEMORY),
+			 errmsg("out of memory")));
+#endif
+	return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ *			Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+	SECStatus		status;
+
+	status = PK11_DigestBegin(ctx->pk11_context);
+
+	if (status != SECSuccess)
+		return -1;
+	return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context.  Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+	SECStatus	status;
+
+	if (ctx == NULL)
+		return -1;
+
+	status = PK11_DigestOp(ctx->pk11_context, data, len);
+	if (status != SECSuccess)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context.  Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+	SECStatus	status;
+	unsigned int outlen;
+
+	switch (ctx->type)
+	{
+		case PG_MD5:
+			if (len < MD5_LENGTH)
+				return -1;
+			break;
+		case PG_SHA1:
+			if (len < SHA1_LENGTH)
+				return -1;
+			break;
+		case PG_SHA224:
+			if (len < SHA224_LENGTH)
+				return -1;
+			break;
+		case PG_SHA256:
+			if (len < SHA256_LENGTH)
+				return -1;
+			break;
+		case PG_SHA384:
+			if (len < SHA384_LENGTH)
+				return -1;
+			break;
+		case PG_SHA512:
+			if (len < SHA512_LENGTH)
+				return -1;
+			break;
+	}
+
+	status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+	if (status != SECSuccess)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+	PRBool		free_context = PR_TRUE;
+
+	if (!ctx)
+		return;
+
+	PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+	ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+	FREE(ctx);
+}
-- 
2.24.3 (Apple Git-128)



  [application/octet-stream] v44-0010-nss-Build-infrastructure.patch (21.4K, ../[email protected]/11-v44-0010-nss-Build-infrastructure.patch)
  download | inline diff:
From 493069fccfd87ef4a6d823680634a2ad0dab3b1c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v44 10/10] nss: Build infrastructure

Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
 configure                     | 294 +++++++++++++++++++++++++++++++++-
 configure.ac                  |  31 +++-
 src/backend/libpq/Makefile    |   4 +
 src/common/Makefile           |   8 +
 src/include/pg_config.h.in    |  12 ++
 src/interfaces/libpq/Makefile |   5 +
 src/tools/msvc/Install.pm     |   3 +-
 src/tools/msvc/Mkvcbuild.pm   |  40 ++++-
 src/tools/msvc/Solution.pm    |  26 +++
 9 files changed, 411 insertions(+), 12 deletions(-)

diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
 LDAP_LIBS_BE
 LDAP_LIBS_FE
 with_ssl
+NSPR_CONFIG
+NSS_CONFIG
 PTHREAD_CFLAGS
 PTHREAD_LIBS
 PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
   --without-zlib          do not use Zlib
   --with-lz4              build with LZ4 support
   --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
-  --with-ssl=LIB          use LIB for SSL/TLS support (openssl)
+  --with-ssl=LIB          use LIB for SSL/TLS support (openssl, nss)
   --with-openssl          obsolete spelling of --with-ssl=openssl
 
 Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
 
 $as_echo "#define USE_OPENSSL 1" >>confdefs.h
 
+elif test "$with_ssl" = nss ; then
+  # TODO: fallback in case nss-config/nspr-config aren't found.
+  if test -z "$NSS_CONFIG"; then
+  for ac_prog in nss-config
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $NSS_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$NSS_CONFIG" && break
+done
+
+else
+  # Report the value of NSS_CONFIG in configure's output in all cases.
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+  if test -z "$NSPR_CONFIG"; then
+  for ac_prog in nspr-config
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $NSPR_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$NSPR_CONFIG" && break
+done
+
+else
+  # Report the value of NSPR_CONFIG in configure's output in all cases.
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+  if test -n "$NSS_CONFIG"; then
+    NSS_LIBS=`$NSS_CONFIG --libs`
+	NSS_CFLAGS=`$NSS_CONFIG --cflags`
+  fi
+  if test -n "$NSPR_CONFIG"; then
+	NSPR_LIBS=`$NSPR_CONFIG --libs`
+	NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+  fi
+
+  LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+  CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_nss3_NSS_InitContext=yes
+else
+  ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+  LIBS="-lnss3 $LIBS"
+
+else
+  as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+  ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+  LIBS="-lnspr4 $LIBS"
+
+else
+  as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+  ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+  LIBS="-lssl3 $LIBS"
+
+else
+  as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
 elif test "$with_ssl" != no ; then
-  as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+  as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
 fi
 
 
@@ -13681,6 +13949,23 @@ else
 fi
 
 
+elif test "$with_ssl" = nss ; then
+  ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+  as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+  ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+  as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
 fi
 
 if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
 if test x"$with_ssl" = x"openssl" ; then
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
 $as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
 elif test x"$PORTNAME" = x"win32" ; then
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
 $as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
   if test x"$ac_cv_file__dev_urandom" = x"no" ; then
     as_fn_error $? "
 no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
   fi
 fi
 
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
 #
 # There is currently only one supported SSL/TLS library: OpenSSL.
 #
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
 if test x"$with_ssl" = x"" ; then
   with_ssl=no
 fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
   # function was removed.
   AC_CHECK_FUNCS([CRYPTO_lock])
   AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+  # TODO: fallback in case nss-config/nspr-config aren't found.
+  PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+  PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+  if test -n "$NSS_CONFIG"; then
+    NSS_LIBS=`$NSS_CONFIG --libs`
+	NSS_CFLAGS=`$NSS_CONFIG --cflags`
+  fi
+  if test -n "$NSPR_CONFIG"; then
+	NSPR_LIBS=`$NSPR_CONFIG --libs`
+	NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+  fi
+
+  LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+  CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+  AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+  AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+  AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+  AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
 elif test "$with_ssl" != no ; then
-  AC_MSG_ERROR([--with-ssl must specify openssl])
+  AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
 fi
 AC_SUBST(with_ssl)
 
@@ -1459,6 +1479,9 @@ fi
 if test "$with_ssl" = openssl ; then
   AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
   AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+  AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+  AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
 fi
 
 if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
 AC_MSG_CHECKING([which random number source to use])
 if test x"$with_ssl" = x"openssl" ; then
   AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+  AC_MSG_RESULT([NSS])
 elif test x"$PORTNAME" = x"win32" ; then
   AC_MSG_RESULT([Windows native])
 else
@@ -2260,7 +2285,7 @@ else
   if test x"$ac_cv_file__dev_urandom" = x"no" ; then
     AC_MSG_ERROR([
 no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
   fi
 fi
 
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
 
 ifeq ($(with_ssl),openssl)
 OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
 endif
 
 ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
 	cryptohash_openssl.o \
 	hmac_openssl.o
 else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+	hmac.o \
+	cipher_nss.o \
+	protocol_nss.o \
+	cryptohash_nss.o
+else
 OBJS_COMMON += \
 	cryptohash.o \
 	hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
 	sha1.o \
 	sha2.o
 endif
+endif
 
 # A few files are currently only built for frontend, not server
 # (Mkvcbuild.pm has a copy of this list, too).  logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
 /* Define to 1 if you have the `m' library (-lm). */
 #undef HAVE_LIBM
 
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
 /* Define to 1 if you have the `pam' library (-lpam). */
 #undef HAVE_LIBPAM
 
@@ -352,6 +358,9 @@
 /* Define to 1 if you have the `ssl' library (-lssl). */
 #undef HAVE_LIBSSL
 
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
 /* Define to 1 if you have the `wldap32' library (-lwldap32). */
 #undef HAVE_LIBWLDAP32
 
@@ -926,6 +935,9 @@
 /* Define to select named POSIX semaphores. */
 #undef USE_NAMED_POSIX_SEMAPHORES
 
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
 /* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
 #undef USE_OPENSSL
 
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
 	fe-secure-openssl.o
 endif
 
+ifeq ($(with_ssl), nss)
+OBJS += \
+	fe-secure-nss.o
+endif
+
 ifeq ($(with_gssapi),yes)
 OBJS += \
 	fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
 		{
 			# These configuration-based exclusions must match vcregress.pl
 			next if ($d eq "uuid-ossp"  && !defined($config->{uuid}));
-			next if ($d eq "sslinfo"    && !defined($config->{openssl}));
+			next if ($d eq "sslinfo"    && !defined($config->{openssl})
+			  && !defined($config->{nss}));
 			next if ($d eq "xml2"       && !defined($config->{xml}));
 			next if ($d =~ /_plperl$/   && !defined($config->{perl}));
 			next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
 		push(@pgcommonallfiles, 'hmac_openssl.c');
 		push(@pgcommonallfiles, 'protocol_openssl.c');
 	}
+	elsif ($solution->{options}->{nss})
+	{
+		push(@pgcommonallfiles, 'cryptohash_nss.c');
+		push(@pgcommonallfiles, 'cipher_nss.c');
+		push(@pgcommonallfiles, 'protocol_nss.c');
+	}
 	else
 	{
 		push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
 	$postgres->FullExportDLL('postgres.lib');
 
 	# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
-	# if building without OpenSSL.
-	if (!$solution->{options}->{openssl})
+	# if building without various options.
+	if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
 	{
 		$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+	}
+	if (!$solution->{options}->{openssl})
+	{
 		$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
 	}
+	if (!$solution->{options}->{nss})
+	{
+		$postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+	}
 	if (!$solution->{options}->{gss})
 	{
 		$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
 	$libpq->AddReference($libpgcommon, $libpgport);
 
 	# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
-	# if building without OpenSSL.
-	if (!$solution->{options}->{openssl})
+	# if building without various options
+	if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
 	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+	}
+	if (!$solution->{options}->{openssl})
+	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
 	}
+	if (!$solution->{options}->{nss})
+	{
+		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+	}
 	if (!$solution->{options}->{gss})
 	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
 		push @contrib_excludes, 'xml2';
 	}
 
+	if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+	{
+		push @contrib_excludes, 'sslinfo';
+	}
+
 	if (!$solution->{options}->{openssl})
 	{
-		push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+		push @contrib_excludes, 'ssl_passphrase_callback';
 	}
 
 	if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
 		$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
 			'pgp-mpi-openssl.c');
 	}
+	elsif ($solution->{options}->{nss})
+	{
+		$pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+			'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+	}
 	else
 	{
 		$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
 		HAVE_LIBLDAP                                => undef,
 		HAVE_LIBLZ4                                 => undef,
 		HAVE_LIBM                                   => undef,
+		HAVE_LIBNSPR4								=> undef,
+		HAVE_LIBNSS3								=> undef,
 		HAVE_LIBPAM                                 => undef,
 		HAVE_LIBREADLINE                            => undef,
 		HAVE_LIBSELINUX                             => undef,
 		HAVE_LIBSSL                                 => undef,
+		HAVE_LIBSSL3								=> undef,
 		HAVE_LIBWLDAP32                             => undef,
 		HAVE_LIBXML2                                => undef,
 		HAVE_LIBXSLT                                => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
 		USE_LLVM                   => undef,
 		USE_NAMED_POSIX_SEMAPHORES => undef,
 		USE_OPENSSL                => undef,
+		USE_NSS                    => undef,
 		USE_PAM                    => undef,
 		USE_SLICING_BY_8_CRC32C    => undef,
 		USE_SSE42_CRC32C           => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
 			$define{HAVE_OPENSSL_INIT_SSL}      = 1;
 		}
 	}
+	if ($self->{options}->{nss})
+	{
+		$define{USE_NSS} = 1;
+		$define{HAVE_LIBNSPR4} = 1;
+		$define{HAVE_LIBNSS3} = 1;
+		$define{HAVE_LIBSSL3} = 1;
+	}
 
 	$self->GenerateConfigHeader('src/include/pg_config.h',     \%define, 1);
 	$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
 			}
 		}
 	}
+	if ($self->{options}->{nss})
+	{
+		$proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+		$proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+		foreach my $lib (qw(plds4 plc4 nspr4))
+		{
+			$proj->AddLibrary($self->{options}->{nss} .
+							  '\lib\lib' . "$lib.lib", 0);
+		}
+		foreach my $lib (qw(ssl3 smime3 nss3))
+		{
+			$proj->AddLibrary($self->{options}->{nss} .
+							  '\lib' . "\\$lib.dll.lib", 0);
+		}
+	}
 	if ($self->{options}->{nls})
 	{
 		$proj->AddIncludeDir($self->{options}->{nls} . '\include');
-- 
2.24.3 (Apple Git-128)



view thread (181+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: Support for NSS as a libpq TLS backend
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox