Received: from malur.postgresql.org ([217.196.149.56])
by arkaria.postgresql.org with esmtps
(TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.92)
(envelope-from )
id 1lTEjJ-0002e1-GM
for pgsql-hackers@arkaria.postgresql.org; Mon, 05 Apr 2021 02:12:42 +0000
Received: from localhost ([127.0.0.1] helo=malur.postgresql.org)
by malur.postgresql.org with esmtp (Exim 4.92)
(envelope-from )
id 1lTEjI-0004fH-D3
for pgsql-hackers@arkaria.postgresql.org; Mon, 05 Apr 2021 02:12:40 +0000
Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29])
by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.92)
(envelope-from )
id 1lTEjH-0004f4-UB
for pgsql-hackers@lists.postgresql.org; Mon, 05 Apr 2021 02:12:40 +0000
Received: from out4-smtp.messagingengine.com ([66.111.4.28])
by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.92)
(envelope-from )
id 1lTEj9-0005fN-QU
for pgsql-hackers@lists.postgresql.org; Mon, 05 Apr 2021 02:12:39 +0000
Received: from compute1.internal (compute1.nyi.internal [10.202.2.41])
by mailout.nyi.internal (Postfix) with ESMTP id 39ED05C0075;
Sun, 4 Apr 2021 22:12:30 -0400 (EDT)
Received: from mailfrontend2 ([10.202.2.163])
by compute1.internal (MEProxy); Sun, 04 Apr 2021 22:12:30 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h=
date:from:to:cc:subject:message-id:references:mime-version
:content-type:in-reply-to; s=fm1; bh=lZ3fzC6TFP/g8NMs8nX8/qTsdMg
esFQs0juZ/rUq5Tc=; b=L78sEuYFCur5a5JODVS/cnd1TTwpKKPeOmVDj8OyqT5
WvBU8CqNgFhngYZQMVbEyQGOORfBMwlR0w6RMYEWm7IBgn9F3P05DQUt0NH8VcUx
AeIN7XUL+nKsR6L947nhTPP2RXTOHi6eyNme8o22go8PWNuMtYYVqktlr28IweXn
8ZdS4udJeWq3d6WX+Kz/SghmVKDU0okxUS7KIKaBiIbl465G0L3vZ+t/ROdNhqX+
Q1OaDe0+yHBaiZHoQt2G2z22iO0Vf61m0jzsv9peJMwLagoScpQfnaT6VDN67icU
8eMVZevofQlmEOJFf3uq3dFHRmUC8HTi7R6I76oKB7A==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
messagingengine.com; h=cc:content-type:date:from:in-reply-to
:message-id:mime-version:references:subject:to:x-me-proxy
:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; bh=lZ3fzC
6TFP/g8NMs8nX8/qTsdMgesFQs0juZ/rUq5Tc=; b=OGQqXiyXkE7DswhYCmM83L
MfmVu2NYvJdVLe7XSwGZydaI6NTUfJ6sUVHP3LrjcbIFEODzCHDEBbwimVGfvc4W
PTwjhhxlFUiO8kufW0f6MmLmc5ozKXPnSyB11M/Y3DraZxu3TbSW5aoW9gUFSHjU
v5BExi8SO0I7o81bZ4IFONuJAY7gNXUltSUf9uhW3DWJ7AGgyp6X9CZKa31whHYw
nUqM1QfYHsktdVvqP2/J9KuIZ/3a2Fm676+PVUFW1YK94dKhhjfH5UdkGa1mg51a
JcK445sWA3RktZ1O8mjjmxuyFLVIgaslQb8VhoS2phA1DhWy0F2ZKEJHIko5d04A
==
X-ME-Sender:
X-ME-Proxy-Cause:
gggruggvucftvghtrhhoucdtuddrgeduledrudejuddgheehucetufdoteggodetrfdotf
fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen
uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne
gfrhhlucfvnfffucdljedtmdenucfjughrpeffhffvuffkfhggtggujgesghdtreertddt
vdenucfhrhhomhepofhitghhrggvlhcurfgrqhhuihgvrhcuoehmihgthhgrvghlsehprg
hquhhivghrrdighiiiqeenucggtffrrghtthgvrhhnpedvgeduuefhtdeuleettdevjeeh
heeiveeuieegleetgeeljeelieeuieehgeevhfenucfkphepudduuddruddtvddrudektd
drudekheenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhm
pehmihgthhgrvghlsehprghquhhivghrrdighiii
X-ME-Proxy:
Received: from paquier.xyz (ee0822lan1.rev.em-net.ne.jp [111.102.180.185])
by mail.messagingengine.com (Postfix) with ESMTPA id E55BF108005C;
Sun, 4 Apr 2021 22:12:24 -0400 (EDT)
Date: Mon, 5 Apr 2021 11:12:22 +0900
From: Michael Paquier
To: Daniel Gustafsson
Cc: Stephen Frost ,
"pgsql-hackers@lists.postgresql.org" ,
Jacob Champion ,
"hlinnaka@iki.fi" ,
"andrew.dunstan@2ndquadrant.com" ,
"thomas.munro@gmail.com" ,
"andres@anarazel.de"
Subject: Re: Support for NSS as a libpq TLS backend
Message-ID:
References: <0F5CF84D-26F1-44D0-AEAE-E011D3CEC0E2@yesql.se>
<686dd2b189ec04b161f71481f1f726255ee8224f.camel@vmware.com>
<1b16041447d57b45ca273f41d26f6e298d756f99.camel@vmware.com>
<04E81B50-ACE9-40D3-9C66-7041F969A958@yesql.se>
<21FABD55-5F80-4D8B-B994-3DB81D8742EE@yesql.se>
<20210321234950.GQ20766@tamriel.snowman.net>
<41C49D88-1150-4D9C-A38A-A5ADC09A29B6@yesql.se>
MIME-Version: 1.0
Content-Type: multipart/signed; micalg=pgp-sha512;
protocol="application/pgp-signature"; boundary="eUFCckuXPLLW7hyM"
Content-Disposition: inline
In-Reply-To:
List-Id:
List-Help:
List-Subscribe:
List-Post:
List-Owner:
List-Archive:
Archived-At:
Precedence: bulk
--eUFCckuXPLLW7hyM
Content-Type: multipart/mixed; boundary="uUWQBhmUUr9q0EXI"
Content-Disposition: inline
--uUWQBhmUUr9q0EXI
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
On Mon, Apr 05, 2021 at 12:13:43AM +0200, Daniel Gustafsson wrote:
> Another rebase to cope with recent changes (hmac, ssl tests etc) that
> conflicted and broke this patchset.
Please find an updated set, v35, attached, and my apologies for
breaking again your patch set. While testing this patch set and
adjusting the SSL tests with HEAD, I have noticed what looks like a
bug with the DN mapping that NSS does not run. The connection strings
are the same in v35 and in v34, with dbname only changing in-between.
Just to be sure, because I could have done something wrong with the
rebase of v35, I have done the same test with v34 applied on top of
dfc843d and things are failing. So it seems to me that there is an
issue with the DN mapping part.
--
Michael
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch"
Content-Transfer-Encoding: quoted-printable
=46rom 5db9fbc12897b28d216a1480cd529f0158525a37 Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v35 1/9] 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
---
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1385 +++++++++++++++++
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/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1080 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
16 files changed, 2868 insertions(+), 5 deletions(-)
create mode 100644 src/include/common/nss.h
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/interfaces/libpq/fe-secure-nss.c
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 u=
ndef
+ * 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
+#include
+#include
+#include
+#include
+
+
+#include
+#include
+#include
+#include
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, i=
nt *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 713c34fedd..3507085a44 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -206,6 +206,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
=20
#ifdef USE_SSL
@@ -288,7 +292,7 @@ extern void be_tls_get_peer_serial(Port *port, char *pt=
r, 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_SIG=
NATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -298,6 +302,10 @@ extern char *be_tls_get_certificate_hash(Port *port, s=
ize_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServer=
Start);
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
=20
#endif /* USE_SSL */
=20
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 3ebbc8d665..15bb3c5d77 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_r=
eload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
=20
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 e28c990382..d839738213 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
=20
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 9dc28e19aa..ba4321353e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2802,7 +2802,13 @@ CheckCertAuth(Port *port)
int status_check_usermap =3D STATUS_ERROR;
char *peer_username =3D NULL;
=20
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
=20
/* 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-secur=
e-nss.c
new file mode 100644
index 0000000000..d2222a680f
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1385 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+#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
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 =3D default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context =3D NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, =
void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called =3D 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/Referenc=
e/PR_ImportTCPSocket
+ *
+ * The function is declared in , but as it is a header ma=
rked
+ * 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, voi=
d *arg);
+
+/* ------------------------------------------------------------ */
+/* 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 t=
he
+ * NSPR initialization must happen after the forking of the backend. We co=
uld
+ * 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 =3D SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_=
FALSE);
+ if (status !=3D 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) =3D=3D 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) !=
=3D 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 =3D SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max =3D supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver =3D ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver =3D=3D -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 =3D ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver =3D ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver =3D=3D -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 =3D 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 =3D 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 introdu=
ce
+ * 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=3D728562 For the server si=
de
+ * 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=3D738456
+ *
+ * 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/M=
odule_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length =3D sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) =3D=3D 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database =3D psprintf("sql:%s", ssl_database);
+ nss_context =3D NSS_InitContext(cert_database, "", "", "",
+ ¶ms,
+ 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 =3D 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 =3D 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 =3D 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) !=3D SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) !=3D SECSucces=
s ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) !=3D 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) =3D=3D 0)
+ {
+ status =3D NSS_SetDomesticPolicy();
+ if (status !=3D SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep =3D ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found =3D 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 =3D SSL_GetImplementedCiphers();
+ for (int i =3D 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers =3D pstrdup(SSLCipherSuites);
+
+ for (c =3D strtok(ciphers, sep); c; c =3D strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status =3D SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found =3D true;
+ if (status !=3D 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) !=3D 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 =3D init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) !=3D PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert =3D 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 =3D 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 =3D SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYP=
E);
+ 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 =3D SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status !=3D SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations =3D 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 =3D SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) =
port);
+ if (status !=3D 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 =3D 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 =3D SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status !=3D SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status =3D SSL_ForceHandshake(port->pr_fd);
+ if (status !=3D SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use =3D true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, 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 =3D PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err =3D PR_GetError();
+
+ if (err =3D=3D PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor =3D WL_SOCKET_READABLE;
+ errno =3D EWOULDBLOCK;
+ }
+ else
+ errno =3D 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 =3D 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write =3D PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err =3D PR_GetError();
+
+ if (err =3D=3D PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor =3D WL_SOCKET_WRITEABLE;
+ errno =3D EWOULDBLOCK;
+ }
+ else
+ errno =3D 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;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn =3D NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd =3D NULL;
+ port->ssl_in_use =3D false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context =3D 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
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status =3D SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status !=3D SECSuccess)
+ goto error;
+
+ status =3D SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(sui=
te));
+ if (status !=3D 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 =3D SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status !=3D 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.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status =3D SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status !=3D SECSuccess)
+ goto error;
+
+ status =3D SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(sui=
te));
+ if (status !=3D 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 =3D SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] =3D '\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 =3D SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] =3D '\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 =3D SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] =3D '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel bindi=
ng.
+ */
+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 =3D NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx =3D NULL;
+ unsigned int outlen;
+
+ *len =3D 0;
+ certificate =3D SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag =3D 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 =3D=3D SEC_OID_SHA1 || digest_alg =3D=3D SEC_OID_MD5)
+ {
+ digest_alg =3D SEC_OID_SHA256;
+ digest_len =3D SHA256_LENGTH;
+ }
+
+ ctx =3D PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena =3D PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data =3D PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_le=
n);
+ digest.len =3D digest_len;
+
+ status =3D SECSuccess;
+ status |=3D PK11_DigestBegin(ctx);
+ status |=3D PK11_DigestOp(ctx, certificate->derCert.data, certificate->de=
rCert.len);
+ status |=3D PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status !=3D SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret =3D palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len =3D 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 install=
ing
+ * 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 i=
t'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 dire=
ctly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinAr=
g is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callbac=
k 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 Filedes=
c.
+ */
+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 =3D 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 =3D psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len =3D run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, size=
of(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 =3D (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 cer=
t 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 =3D 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 =3D (Port *) arg;
+
+ port->peer_cert_valid =3D false;
+ return SECFailure;
+}
+
+/*
+ * 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 character=
s or
+ * embedded nulls), with the length of the buffer returned in the len para=
m.
+ * 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 quo=
ting
+ * 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 compatib=
ility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject =3D cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn =3D subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava =3D (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) !=3D SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf =3D 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 =3D MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] =3D '\0';
+
+ *len =3D buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len =3D 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 is=
Server)
+{
+ SECStatus status;
+ Port *port =3D (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status =3D SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, chec=
ksig, PR_TRUE);
+ if (status !=3D SECSuccess)
+ return status;
+
+ port->peer_cn =3D NULL;
+ port->peer_cert_valid =3D false;
+
+ cert =3D SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert !=3D NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn =3D raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid =3D true;
+
+ status =3D SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks li=
ke
+ * CVE-2009-4034.
+ */
+ if (len !=3D 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 =3D SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn =3D peer_cn;
+ port->peer_cert_valid =3D true;
+
+ status =3D 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 =3D 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 =3D PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close =3D 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 =3D PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id =3D=3D 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 =3D 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 =3D (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_ErrorToStrin=
g 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_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in u=
se
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdo=
wn
+ * regardless so we may as well clean up the best we can. Returning SECFai=
lure
+ * will cause the NSS shutdown to return with an error, but it will shutdo=
wn
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port =3D (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 =3D false;
#endif
+char *ssl_database;
=20
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites =3D NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c9c9da85f3..7a25f36cd1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4370,7 +4370,11 @@ static struct config_string ConfigureNamesString[] =
=3D
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4438,6 +4442,16 @@ static struct config_string ConfigureNamesString[] =
=3D
check_canonical_path, assign_pgstat_temp_directory, NULL
},
=20
+ {
+ {"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 poten=
tial synchronous ones."),
@@ -4466,8 +4480,10 @@ static struct config_string ConfigureNamesString[] =
=3D
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[] =3D {
+
+ {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_LE=
NGTH},
+
+ {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_LE=
NGTH},
+
+ {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/sslprot=
o.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 addressi=
ng
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps l=
ess?
+ */
+static const NSSCiphers NSS_CipherList[] =3D {
+
+ {"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_SHA2=
56},
+ {"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_C=
BC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_C=
BC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_C=
BC_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_SHA2=
56},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA2=
56},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA2=
56},
+
+ {"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_C=
BC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_C=
BC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_C=
BC_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_SHA2=
56},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA3=
84},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA2=
56},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA3=
84},
+
+ {"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 =3D NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) =3D=3D 0)
+ {
+ *cipher =3D cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int=
*len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate =3D NSS_SCRAMDigestAlgorithm;
+
+ for (candidate =3D NSS_SCRAMDigestAlgorithm; candidate->signature; candid=
ate++)
+ {
+ if (signature =3D=3D candidate->signature)
+ {
+ *algorithm =3D candidate->hash;
+ *len =3D 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/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-co=
nnect.c
index 56a8266bc3..61d736fa7e 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -341,6 +341,10 @@ static const internalPQconninfoOption PQconninfoOption=
s[] =3D {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") =3D 15 */
offsetof(struct pg_conn, target_session_attrs)},
=20
+ {"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..5f4eb31535
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1080 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *lib=
rary, 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, CER=
TDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool ch=
ecksig, 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/Referenc=
e/PR_ImportTCPSocket
+ *
+ * The function is declared in , but as it is a header ma=
rked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust =3D NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when ther=
e 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 =3D "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name =3D "libnssckbi.dylib";
+#else
+static const char *ca_trust_name =3D "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook =3D NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_In=
it
+ * 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 =3D NULL;
+
+ conn->ssl_in_use =3D false;
+ conn->has_password =3D false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context =3D NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+ /*
+ * 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(¶ms, 0, sizeof(params));
+ params.length =3D sizeof(params);
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path =3D psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context =3D NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶ms,
+ 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 =3D NSS_InitContext("", "", "", "", ¶ms,
+ 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 =3D NSS_SetDomesticPolicy();
+ if (status !=3D 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") =3D=3D 0 ||
+ strcmp(conn->sslmode, "verify-full") =3D=3D 0))
+ {
+ status =3D pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certific=
ates\"");
+ if (status !=3D 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;
+ }
+ }
+
+
+ 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 =3D 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 =3D 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 =3D SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_versi=
on) > 0)
+ {
+ int ssl_min_ver =3D ssl_protocol_param_to_nss(conn->ssl_min_protocol_v=
ersion);
+
+ if (ssl_min_ver =3D=3D -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL pr=
otocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min =3D ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_versi=
on) > 0)
+ {
+ int ssl_max_ver =3D ssl_protocol_param_to_nss(conn->ssl_max_protocol_v=
ersion);
+
+ if (ssl_max_ver =3D=3D -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL pr=
otocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max =3D ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) !=3D 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 =3D 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 =3D SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status !=3D 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 *) c=
onn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status =3D SSL_ForceHandshake(conn->pr_fd);
+
+ if (status !=3D SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use =3D true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno =3D 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 =3D PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread =3D=3D 0)
+ {
+ read_errno =3D ECONNRESET;
+ nread =3D -1;
+ }
+ else if (nread =3D=3D -1)
+ {
+ status =3D PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread =3D 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno =3D 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 =3D ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread =3D=3D -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.
+ *
+ * This is part of the PostgreSQL TLS backend API.
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ unsigned char c;
+ int n;
+
+ /*
+ * PR_Recv peeks into the stream with the timeount turned off, to see if
+ * there is another byte to read off the wire. There is an NSS function
+ * SSL_DataPending() which might seem like a better fit, but it will only
+ * check already encrypted data in the SSL buffer, not still unencrypted
+ * data, thus it doesn't guarantee that a subsequent call to
+ * PR_Read/PR_Recv won't block.
+ */
+ n =3D PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ return (n > 0);
+}
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno =3D 0;
+
+ n =3D PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status =3D PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno =3D EAGAIN;
+#else
+ write_errno =3D EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno =3D 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;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len =3D 0;
+
+ server_cert =3D SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag =3D 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));
+ return NULL;
+ }
+
+ arena =3D PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data =3D PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_le=
n);
+ digest.len =3D digest_len;
+
+ status =3D PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.dat=
a,
+ server_cert->derCert.len);
+
+ if (status !=3D SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret =3D pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len =3D digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are conside=
red.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname =3D NULL;
+ CERTCertificate *server_cert =3D NULL;
+ SECStatus status =3D SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena =3D NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname =3D SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] =3D=3D '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert =3D SSL_PeerCertificate(conn->pr_fd);
+ status =3D CERT_VerifyCertName(server_cert, server_hostname);
+ if (status !=3D SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status =3D CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_N=
AME,
+ &altname_item);
+ if (status =3D=3D SECSuccess)
+ {
+ arena =3D PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status =3D SECFailure;
+ goto done;
+ }
+ san_list =3D CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status =3D SECFailure;
+ goto done;
+ }
+
+ for (cn =3D san_list; cn !=3D san_list; cn =3D CERT_GetNextGeneralName(c=
n))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status =3D CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status !=3D SECSuccess)
+ goto done;
+
+ rv =3D pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name =3D alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv =3D=3D 1)
+ status =3D SECSuccess;
+ else
+ {
+ status =3D SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() =3D=3D SEC_ERROR_EXTENSION_NOT_FOUND)
+ status =3D SECSuccess;
+ else
+ status =3D SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status =3D=3D 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 =3D PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error =3D 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 =3D psprintf("library=3D\"%s\", name=3D\"%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 =3D SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module =3D 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 funct=
ion
+ * 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 is=
Server)
+{
+ SECStatus status;
+ PGconn *conn =3D (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin =3D SSL_RevealPinArg(conn->pr_fd);
+ server_cert =3D 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 =3D CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefault=
CertDB(),
+ 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 =3D=3D SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status =3D SECFailure;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ 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 inv=
oke
+ * it ourselves to ensure we can do cert authentication. A TODO is to supp=
ort
+ * running without a specified sslcert parameter. By retrieving all the ce=
rts
+ * via nickname from the cert database and see if we find one which apply =
with
+ * NSS_CmpCertChainWCANames() and PK11_FindKeyByAnyCert() we could support
+ * just running with a ssl database specified.
+ *
+ * For now, we use the default client certificate validation which require=
s a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNam=
es,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn =3D (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pR=
etKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate fa=
iled
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn =3D (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;
+
+ /*
+ * 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") =3D=3D 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") =3D=3D 0 ||
+ strcmp(conn->sslmode, "verify-ca") =3D=3D 0)
+ return SECFailure;
+
+ err =3D PORT_GetError();
+
+ /*
+ * 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:
+ return SECSuccess;
+ break;
+ default:
+ return SECFailure;
+ break;
+ }
+
+ /* Unreachable */
+ return SECSuccess;
+}
+
+/* ------------------------------------------------------------ */
+/* 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 warn=
ing
+ * 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") =3D=3D 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] =3D {
+ "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") =3D=3D 0)
+ return "NSS";
+
+ status =3D SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status !=3D SECSuccess)
+ return NULL;
+
+ status =3D SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(sui=
te));
+ if (status !=3D SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") =3D=3D 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") =3D=3D 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBit=
s);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") =3D=3D 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 checking.
+ */
+ if (strcmp(attribute_name, "compression") =3D=3D 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 param.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) =3D=3D 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) =3D=3D 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) =3D=3D 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) =3D=3D 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 it's 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 =3D PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR=
_FALSE, (void *) conn));
+ else
+ certificates =3D PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA =3D !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 =3D 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 invoke=
d.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn =3D (PGconn *) arg;
+
+ conn->has_password =3D 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 leg=
acy
+ * only have support for a single password, there is little reason to insp=
ect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or wit=
h 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 to=
o.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn =3D (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry !=3D 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-sec=
ure.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, P=
Gconn *conn)
}
#endif /* USE_OPENSSL */
=20
+#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 s=
upport */
#ifndef ENABLE_GSS
=20
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-f=
e.h
index 227adde5a5..2ade6f8445 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -650,6 +650,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHo=
ok_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook=
);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *co=
nn);
=20
+/* =3D=3D in fe-secure-nss.c =3D=3D=3D */
+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 ret=
ry, 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 3f7907127e..750afe54b3 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
=20
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -383,6 +387,7 @@ struct pg_conn
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
+ 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 */
@@ -523,6 +528,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >=3D 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 */
=20
#ifdef ENABLE_GSS
@@ -784,7 +811,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *pt=
r, 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_SIG=
NATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postg=
res_fdw/expected/postgres_fdw.out
index 59e4e27ffb..2d374f5040 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8913,7 +8913,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_bindi=
ng, connect_timeout, dbname, host, hostaddr, port, options, application_nam=
e, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_=
user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl=
, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_versio=
n, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimat=
e, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, bat=
ch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_bindi=
ng, connect_timeout, dbname, host, hostaddr, port, options, application_nam=
e, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_=
user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl=
, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_versio=
n, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_r=
emote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fe=
tch_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 diff=
erent
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0002-Refactor-SSL-testharness-for-multiple-library.patch"
Content-Transfer-Encoding: quoted-printable
=46rom de8baa6ebe0978c097303bcd04efd0a2a82f2474 Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v35 2/9] 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/
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 =3D> 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 =3D> SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 21865ac30f..3d5ede4366 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,12 +4,10 @@ use PostgresNode;
use TestLib;
use Test::More;
=20
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
=20
-use SSLServer;
+use SSL::Server;
=20
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR =3D '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
=20
-# 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 =3D (
- "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 cha=
nge: $!";
- 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.
=20
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
=20
# Run this before we lock down access below.
my $result =3D $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
=20
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
=20
note "testing password-protected keys";
=20
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=3Don\n";
-print $sslconf "ssl_cert_file=3D'server-cn-only.crt'\n";
-print $sslconf "ssl_key_file=3D'server-password.key'\n";
-print $sslconf "ssl_passphrase_command=3D'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);
=20
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=3Don\n";
-print $sslconf "ssl_cert_file=3D'server-cn-only.crt'\n";
-print $sslconf "ssl_key_file=3D'server-password.key'\n";
-print $sslconf "ssl_passphrase_command=3D'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');
@@ -567,7 +529,5 @@ $node->connect_fails(
expected_stderr =3D> qr/SSL error: sslv3 alert certificate revoked/);
=20
# 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 583b62b3a1..0901d3b2e5 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,11 +11,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
=20
-use SSLServer;
+use SSL::Server;
=20
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all =3D> 'OpenSSL not supported by this build';
+ plan skip_all =3D> 'SSL not supported by this build';
}
=20
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ $node->connect_fails(
=20
# 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/Bac=
kend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA =3D qw(Exporter);
+our @EXPORT_OK =3D qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys =3D (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) =3D @_;
+
+ my $self =3D { _library =3D> 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class =3D 'SSL::Backend::OpenSSL';
+
+ my $backend =3D $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions ch=
ange: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self =3D $_[0];
+ my $certfile =3D $_[1];
+ my $cafile =3D $_[2] || "root+client_ca";
+ my $keyfile =3D $_[3] || $certfile;
+
+ my $sslconf =3D
+ "ssl_ca_file=3D'$cafile.crt'\n"
+ . "ssl_cert_file=3D'$certfile.crt'\n"
+ . "ssl_key_file=3D'$keyfile.key'\n"
+ . "ssl_crl_file=3D'root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) =3D @_;
+
+ 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 129a7154a9..19ccdd95f9 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
=20
-package SSLServer;
+package SSL::Server;
=20
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 f=
or
+# the user, apart from individual configuration settings, so keep the back=
end
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend =3D get_new_openssl_backend();
+ $openssl =3D 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend =3D get_new_nss_backend();
+ $nss =3D 1;
+}
=20
use Exporter 'import';
our @EXPORT =3D qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
=20
@@ -109,14 +129,21 @@ sub configure_test_server_for_ssl
close $sslconf;
=20
# Copy all server certificates and keys, and client root cert, to the dat=
a 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";
+ }
=20
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -124,20 +151,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=3Don
configure_hba_for_ssl($node, $servercidr, $authmethod);
=20
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
=20
-# 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 =3D $_[0];
my $certfile =3D $_[1];
my $cafile =3D $_[2] || "root+client_ca";
+ my $keyfile =3D $_[3] || '';
+ my $pwcmd =3D $_[4] || '';
my $crlfile =3D "root+client.crl";
my $crldir;
my $pgdata =3D $node->data_dir;
=20
+ $keyfile =3D $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -147,13 +190,23 @@ sub switch_server_cert
=20
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=3Don\n";
- print $sslconf "ssl_ca_file=3D'$cafile.crt'\n";
- print $sslconf "ssl_cert_file=3D'$certfile.crt'\n";
- print $sslconf "ssl_key_file=3D'$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file=3D'$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir=3D'$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command=3D'$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
=20
+# 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 =3D $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0003-nss-Add-NSS-specific-tests.patch"
Content-Transfer-Encoding: quoted-printable
=46rom 6d28d68f1adc2041aef66ba3c70c269919002d8d Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v35 3/9] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 244 +++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 372 ++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 35 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 585 insertions(+), 194 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 a517756c94..c2f783bd1b 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES :=3D $(CERTIFICATES:%=3Dssl/%.key) $(CERTIFICAT=
ES:%=3Dssl/%.crt) \
SSLDIRS :=3D ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
=20
+# 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 sho=
uld
+# contain the files included for easier test suite code reading.
+NSSFILES :=3D ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we j=
ust
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS :=3D ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
=20
+# Generate NSS certificate databases corresponding to the OpenSSL certific=
ates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We do=
n't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt =
ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
=20
+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.c=
rt -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.c=
rt -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.c=
rt -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 se=
rver-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn=
-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-c=
n-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-on=
ly.config
rm ssl/server-ss.csr
=20
+# pk12util won't preserve the password when importing the password protect=
ed
+# key, the password must be set on the database *before* importing it as t=
he
+# 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.c=
rt -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 -passi=
n 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secr=
et1'
+
+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.c=
rt -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-c=
n-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$=
${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: s=
sl/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/serve=
r-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 -ce=
rtfile 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/ser=
ver-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-s=
ingle-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 ss=
l/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/s=
erver-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 ss=
l/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.c=
rt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names=
=2Ecrt -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.c=
rt -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 -pa=
ssout 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'
@@ -98,6 +241,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 P=
EM cert
rm ssl/client-dn.csr ssl/temp.crt
=20
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -=
t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in =
ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t=
"CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt =
-t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+serv=
er_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/c=
lient-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -pas=
sin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -=
k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.co=
nfig
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr =
-config client.config
@@ -105,6 +269,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/cli=
ent_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
=20
+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.c=
rt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-=
revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout=
pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +309,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca=
=2Ecrt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
=20
+# 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.c=
rt -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
=20
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
=20
+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
=20
+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
=20
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +368,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/cli=
ent.crl`.r0
=20
+#### 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=3DTest CA for PostgreSQL SSL regression tests,OU=3DPostgreSQL 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/nativ=
e_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=3Dcommon-name.pg-ssltest.test,OU=3DPostgreSQL 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,digi=
talSignature,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-$*=
=2Ecrt -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=3Dssltestuser,OU=
=3DPostgreSQL 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 --n=
sCertType 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 C=
Tu,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-certi=
ndex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/t=
emp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
=20
+.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
=20
# 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 3d5ede4366..74cf5dcd7c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
=20
use SSL::Server;
=20
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all =3D> 'OpenSSL not supported by this build';
+ $openssl =3D 1;
+ plan tests =3D> 107;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss =3D 1;
+ plan tests =3D> 107;
}
else
{
- plan tests =3D> 103;
+ plan skip_all =3D> 'SSL not supported by this build';
}
=20
#### Some configuration
@@ -51,19 +60,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $=
SERVERHOSTCIDR,
=20
note "testing password-protected keys";
=20
-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);
=20
-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);
+ switch_server_cert($node,
+ certfile =3D> 'server-cn-only',
+ cafile =3D> 'root+client_ca',
+ keyfile =3D> 'server-password',
+ nssdatabase =3D> 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd =3D> 'echo wrongpassword',
+ restart =3D> '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 =3D> 'server-cn-only',
+ cafile =3D> 'root+client_ca',
+ keyfile =3D> 'server-password',
+ nssdatabase =3D> 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd =3D> 'echo secret1',
+ restart =3D> 'no');
+
+ 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 =3D> 'server-cn-only',
+ cafile =3D> 'root+client_ca',
+ keyfile =3D> 'server-password',
+ nssdatabase =3D> 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd =3D> 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid hostaddr=3D$SERVE=
RHOSTADDR host=3Dcommon-name.pg-ssltest.test sslrootcert=3Dinvalid sslmode=
=3Drequire",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile =3D> 'server-cn-only',
+ cafile =3D> 'root+client_ca',
+ keyfile =3D> 'server-password',
+ nssdatabase =3D> 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd =3D> 'echo secret1');
+
+ $node->connect_ok(
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid hostaddr=3D$SERVE=
RHOSTADDR host=3Dcommon-name.pg-ssltest.test sslrootcert=3Dinvalid sslmode=
=3Drequire",
+ "connect to server with correct key password configured");
+}
=20
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +149,7 @@ command_ok(
=20
note "running client tests";
=20
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile =3D> 'server-cn-only', nssdatabase =3D>=
'server-cn-only.crt__server-cn-only.key.db');
=20
$common_connstr =3D
"user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid hostaddr=3D$SERVE=
RHOSTADDR host=3Dcommon-name.pg-ssltest.test";
@@ -110,87 +168,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=3Dinvalid sslmode=3Dverify-ca",
"connect without server root cert sslmode=3Dverify-ca",
- expected_stderr =3D> qr/root certificate file "invalid" does not exist/);
+ expected_stderr =3D> qr/root certificate file "invalid" does not exist|Pe=
er's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=3Dinvalid sslmode=3Dverify-full",
"connect without server root cert sslmode=3Dverify-full",
- expected_stderr =3D> qr/root certificate file "invalid" does not exist/);
+ expected_stderr =3D> qr/root certificate file "invalid" does not exist|Pe=
er's certificate issuer has been marked as not trusted by the user/);
=20
# 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=3Dssl/client_ca.crt sslmode=3Drequire",
+ "$common_connstr sslrootcert=3Dssl/client_ca.crt sslmode=3Drequire ssldat=
abase=3Dssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=3Drequire",
- expected_stderr =3D> qr/SSL error: certificate verify failed/);
+ expected_stderr =3D> qr/SSL error: certificate verify failed|Peer's certi=
ficate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/client_ca.crt sslmode=3Dverify-ca",
+ "$common_connstr sslrootcert=3Dssl/client_ca.crt sslmode=3Dverify-ca ssld=
atabase=3Dssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=3Dverify-ca",
- expected_stderr =3D> qr/SSL error: certificate verify failed/);
+ expected_stderr =3D> qr/SSL error: certificate verify failed|Peer's certi=
ficate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/client_ca.crt sslmode=3Dverify-full",
+ "$common_connstr sslrootcert=3Dssl/client_ca.crt sslmode=3Dverify-full ss=
ldatabase=3Dssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=3Dverify-full",
- expected_stderr =3D> qr/SSL error: certificate verify failed/);
+ expected_stderr =3D> qr/SSL error: certificate verify failed|Peer's certi=
ficate issuer has been marked as not trusted by the user/);
=20
-# 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=3Dssl/server_ca.crt sslmode=3Dverify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr =3D> 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=3Dssl/server_ca.crt sslmode=3Dverify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr =3D> qr/SSL error: certificate verify failed/);
+}
=20
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sldatabase=3Dssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=3Drequire");
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
ssldatabase=3Dssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=3Dverify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-fu=
ll",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-fu=
ll ssldatabase=3Dssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=3Dverify-full");
=20
-# Test with cert root file that contains two certificates. The client shou=
ld
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/both-cas-1.crt sslmode=3Dverify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/both-cas-2.crt sslmode=3Dverify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
=20
+ # Test with cert root file that contains two certificates. The client sho=
uld
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=3Dssl/both-cas-1.crt sslmode=3Dverify-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=3Dssl/both-cas-2.crt sslmode=3Dverify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
=20
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dinvalid",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dinvalid ssldatabase=3Dssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
=20
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr =3D> 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=3Dssl/root+server_ca.crt sslmode=3Dverify-c=
a sslcrl=3Dssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr =3D> qr/SSL error: certificate verify failed/);
=20
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrldir=3Dssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr =3D> qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-c=
a sslcrldir=3Dssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr =3D> qr/SSL error: certificate verify failed/);
+}
=20
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dssl/root+server.crl",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dssl/root+server.crl ssldatabase=3Dssl/nss/root+server_ca.crt__roo=
t+server.crl.db",
"CRL with a non-revoked cert");
=20
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrldir=3Dssl/root+server-crldir",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrldir=3Dssl/root+server-crldir ssldatabase=3Dssl/nss/root+server_ca.cr=
t__root+server.crldir.db",
"directory CRL with a non-revoked cert");
=20
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =3D
- "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR";
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/nss/root+s=
erver_ca.crt.db";
=20
$node->connect_ok("$common_connstr sslmode=3Drequire host=3Dwronghost.test=
",
"mismatch between host name and server certificate sslmode=3Drequire");
@@ -200,15 +275,13 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslmode=3Dverify-full host=3Dwronghost.test",
"mismatch between host name and server certificate sslmode=3Dverify-full",
- expected_stderr =3D>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not matc=
h host name "wronghost.test"\E/
-);
+ expected_stderr =3D> qr/\Qserver certificate for "common-name.pg-ssltest.=
test" does not match host name "wronghost.test"\E|requested domain name doe=
s not match the server's certificate/);
=20
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile =3D> 'server-multiple-alt-names', nssda=
tabase =3D> 'server-multiple-alt-names.crt__server-multiple-alt-names.key.d=
b');
=20
$common_connstr =3D
- "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR sslmode=3Dverify-full";
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR sslmode=3Dverify-full ssldat=
abase=3Dssl/nss/root+server_ca.crt.db";
=20
$node->connect_ok(
"$common_connstr host=3Ddns1.alt-name.pg-ssltest.test",
@@ -222,22 +295,18 @@ $node->connect_ok("$common_connstr host=3Dfoo.wildcar=
d.pg-ssltest.test",
$node->connect_fails(
"$common_connstr host=3Dwronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
- expected_stderr =3D>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 othe=
r names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
-);
+ expected_stderr =3D> qr/\Qserver certificate for "dns1.alt-name.pg-ssltes=
t.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 certific=
ate/);
$node->connect_fails(
"$common_connstr host=3Ddeep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
- expected_stderr =3D>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 othe=
r names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"=
\E/
-);
+ expected_stderr =3D> qr/\Qserver certificate for "dns1.alt-name.pg-ssltes=
t.test" (and 2 other names) does not match host name "deep.subdomain.wildca=
rd.pg-ssltest.test"\E|requested domain name does not match the server's cer=
tificate/);
=20
# 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 =3D> 'server-single-alt-name', nssdatab=
ase =3D> 'server-single-alt-name.crt__server-single-alt-name.key.db');
=20
$common_connstr =3D
- "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR sslmode=3Dverify-full";
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR sslmode=3Dverify-full ssldat=
abase=3Dssl/nss/root+server_ca.crt.db";
=20
$node->connect_ok(
"$common_connstr host=3Dsingle.alt-name.pg-ssltest.test",
@@ -246,22 +315,19 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr host=3Dwronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
- expected_stderr =3D>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not =
match host name "wronghost.alt-name.pg-ssltest.test"\E/
-);
+ expected_stderr =3D> qr/\Qserver certificate for "single.alt-name.pg-sslt=
est.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|r=
equested domain name does not match the server's certificate/);
$node->connect_fails(
"$common_connstr host=3Ddeep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wild=
card",
- expected_stderr =3D>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not =
match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ expected_stderr =3D> qr/\Qserver certificate for "single.alt-name.pg-sslt=
est.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test=
"\E|requested domain name does not match the server's certificate/
);
=20
# 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 =3D> 'server-cn-and-alt-names', nssdata=
base =3D> 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
=20
$common_connstr =3D
- "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR sslmode=3Dverify-full";
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR sslmode=3Dverify-full ssldat=
abase=3Dssl/nss/root+server_ca.crt.db";
=20
$node->connect_ok("$common_connstr host=3Ddns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -270,44 +336,40 @@ $node->connect_ok("$common_connstr host=3Ddns2.alt-na=
me.pg-ssltest.test",
$node->connect_fails(
"$common_connstr host=3Dcommon-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
- expected_stderr =3D>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 othe=
r name) does not match host name "common-name.pg-ssltest.test"\E/
-);
+ expected_stderr =3D> qr/\Qserver certificate for "dns1.alt-name.pg-ssltes=
t.test" (and 1 other name) does not match host name "common-name.pg-ssltest=
=2Etest"\E|requested domain name does not match the server's certificate/);
=20
# Finally, test a server certificate that has no CN or SANs. Of course, th=
at's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile =3D> 'server-no-names', nssdatabase =3D=
> 'server-no-names.crt__server-no-names.key.db');
$common_connstr =3D
- "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR";
+ "user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid sslrootcert=3Dssl=
/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/nss/root+s=
erver_ca.crt.db";
=20
$node->connect_ok(
"$common_connstr sslmode=3Dverify-ca host=3Dcommon-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=3Dverify-ca");
$node->connect_fails(
- $common_connstr . " "
- . "sslmode=3Dverify-full host=3Dcommon-name.pg-ssltest.test",
+ "$common_connstr sslmode=3Dverify-full host=3Dcommon-name.pg-ssltest.test=
",
"server certificate without CN or SANs sslmode=3Dverify-full",
- expected_stderr =3D>
- qr/could not get server's host name from server certificate/);
+ expected_stderr =3D> qr/could not get server's host name from server cert=
ificate|requested domain name does not match the server's certificate./);
=20
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile =3D> 'server-revoked', nssdatabase =3D>=
'server-revoked.crt__server-revoked.key.db');
=20
$common_connstr =3D
"user=3Dssltestuser dbname=3Dtrustdb sslcert=3Dinvalid hostaddr=3D$SERVE=
RHOSTADDR host=3Dcommon-name.pg-ssltest.test";
=20
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
ssldatabase=3Dssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dssl/root+server.crl",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrl=3Dssl/server.crl ssldatabase=3Dssl/nss/root+server_ca.crt__server.c=
rl.db",
"does not connect with client-side CRL file",
- expected_stderr =3D> qr/SSL error: certificate verify failed/);
+ expected_stderr =3D> qr/SSL error: certificate verify failed|SSL error: P=
eer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrldir=3Dssl/root+server-crldir",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Dverify-ca=
sslcrldir=3Dssl/root+server-crldir ssldatabase=3Dssl/nss/root+server_ca.cr=
t__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr =3D> qr/SSL error: certificate verify failed/);
+ expected_stderr =3D> qr/SSL error: certificate verify failed|SSL error: P=
eer's Certificate has been revoked/);
=20
# pg_stat_ssl
command_like(
@@ -325,29 +387,46 @@ command_like(
=20
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_min_protocol_version=3DTLSv1.2 ssl_max_protocol_version=3DTLSv1.2",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_min_protocol_version=3DTLSv1.2 ssl_max_protocol_version=3DTLSv1.2 ssldat=
abase=3Dssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_min_protocol_version=3DTLSv1.2 ssl_max_protocol_version=3DTLSv1.1",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_min_protocol_version=3DTLSv1.2 ssl_max_protocol_version=3DTLSv1.1 ssldat=
abase=3Dssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr =3D> qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_min_protocol_version=3Dincorrect_tls",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_min_protocol_version=3Dincorrect_tls ssldatabase=3Dssl/nss/root+server_c=
a.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr =3D> qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_max_protocol_version=3Dincorrect_tls",
+ "$common_connstr sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire s=
sl_max_protocol_version=3Dincorrect_tls ssldatabase=3Dssl/nss/root+server_c=
a.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr =3D> qr/invalid ssl_max_protocol_version value/);
=20
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile =3D> 'native_server-root', cafile =3D>=
'native_ca-root', nssdatabase =3D> 'native_server-root.db');
+ $common_connstr =3D
+ "user=3Dssltestuser dbname=3Dtrustdb hostaddr=3D$SERVERHOSTADDR host=3D=
common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=3Drequire user=3Dssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
=20
+switch_server_cert($node, certfile =3D> 'server-revoked', nssdatabase =3D>=
'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
=20
$common_connstr =3D
- "sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire dbname=3Dcertdb =
hostaddr=3D$SERVERHOSTADDR";
+ "sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire dbname=3Dcertdb =
hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/nss/client.crt__client.key.db";
=20
# no client cert
$node->connect_fails(
@@ -361,55 +440,67 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM forma=
t"
);
=20
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER forma=
t"
-);
+$common_connstr =3D
+ "sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire dbname=3Dcertdb =
hostaddr=3D$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dss=
l/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER form=
at"
+ );
+}
=20
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client-encrypted-pem_tmp.key sslpassword=3D'dUmmyP^#+'",
+ "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client-encrypted-pem_tmp.key sslpassword=3D'dUmmyP^#+' ssldatabase=3Dssl/n=
ss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted=
PEM format"
);
=20
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client-encrypted-der_tmp.key sslpassword=3D'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=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dss=
l/client-encrypted-der_tmp.key sslpassword=3D'dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypte=
d DER format"
+ );
+}
=20
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client-encrypted-pem_tmp.key sslpassword=3D'wrong'",
+ "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client-encrypted-pem_tmp.key sslpassword=3D'wrong' ssldatabase=3Dssl/nss/c=
lient.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong passw=
ord in encrypted PEM format",
- expected_stderr =3D>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ expected_stderr =3D>qr!connection requires a valid client certificate|\Qp=
rivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
);
=20
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
=20
-# correct client cert using whole DN
-my $dn_connstr =3D "$common_connstr dbname=3Dcertdb_dn";
+ # correct client cert using whole DN
+ my $dn_connstr =3D "$common_connstr dbname=3Dcertdb_dn";
=20
-$node->connect_ok(
- "$dn_connstr user=3Dssltestuser sslcert=3Dssl/client-dn.crt sslkey=3Dssl/=
client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping");
+ $node->connect_ok(
+ "$dn_connstr user=3Dssltestuser sslcert=3Dssl/client-dn.crt sslkey=3Dssl=
/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping");
=20
-# same thing but with a regex
-$dn_connstr =3D "$common_connstr dbname=3Dcertdb_dn_re";
+ # same thing but with a regex
+ $dn_connstr =3D "$common_connstr dbname=3Dcertdb_dn_re";
=20
-$node->connect_ok(
- "$dn_connstr user=3Dssltestuser sslcert=3Dssl/client-dn.crt sslkey=3Dssl/=
client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr =3D "$common_connstr dbname=3Dcertdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=3Dssltestuser sslcert=3Dssl/client-dn.crt sslkey=3Dssl/=
client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping");
+ $node->connect_ok(
+ "$dn_connstr user=3Dssltestuser sslcert=3Dssl/client-dn.crt sslkey=3Dssl=
/client-dn_tmp.key",
+ "certificate authorization succeeds with DN regex mapping");
=20
+ # same thing but using explicit CN
+ $dn_connstr =3D "$common_connstr dbname=3Dcertdb_cn";
=20
+ $node->connect_ok(
+ "$dn_connstr user=3Dssltestuser sslcert=3Dssl/client-dn.crt sslkey=3Dssl=
/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
=20
TODO:
{
@@ -447,18 +538,19 @@ command_like(
'-P',
'null=3D_null_',
'-d',
- "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dss=
l/client_tmp.key",
+ "$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dss=
l/client_tmp.key ssldatabase=3Dssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid =3D pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=3Dssltestuser,1,\Q/CN=3DTest CA for P=
ostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=3Dssltestuser,1,/?\QCN=3DTest CA for=
PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
=20
# 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);
=20
$node->connect_fails(
"$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dss=
l/client_wrongperms_tmp.key",
@@ -472,20 +564,23 @@ SKIP:
$node->connect_fails(
"$common_connstr user=3Danotheruser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client_tmp.key",
"certificate authorization fails with client cert belonging to another us=
er",
- expected_stderr =3D>
- qr/certificate authentication failed for user "anotheruser"/);
+ expected_stderr =3D> qr/unable to verify certificate|certificate authenti=
cation failed for user "anotheruser"/
+);
+
+$common_connstr =3D
+ "sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire dbname=3Dcertdb =
hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/nss/client-revoked.crt__client=
-revoked.key.db";
=20
# revoked client cert
$node->connect_fails(
"$common_connstr user=3Dssltestuser sslcert=3Dssl/client-revoked.crt sslk=
ey=3Dssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr =3D> qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =3D> qr/SSL error: sslv3 alert certificate revoked|SSL er=
ror: Peer's certificate issuer has been marked as not trusted by the user/);
=20
# Check that connecting with auth-option verify-full in pg_hba:
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =3D
- "sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire dbname=3Dverifyd=
b hostaddr=3D$SERVERHOSTADDR";
+ "sslrootcert=3Dssl/root+server_ca.crt sslmode=3Drequire dbname=3Dverifyd=
b hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/nss/client.crt__client.key.d=
b";
=20
$node->connect_ok(
"$common_connstr user=3Dssltestuser sslcert=3Dssl/client.crt sslkey=3Dssl=
/client_tmp.key",
@@ -506,9 +601,9 @@ $node->connect_ok(
);
=20
# 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 =3D> 'server-cn-only', cafile =3D> 'roo=
t_ca', nssdatabase =3D> 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =3D
- "user=3Dssltestuser dbname=3Dcertdb sslkey=3Dssl/client_tmp.key sslrootc=
ert=3Dssl/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR";
+ "user=3Dssltestuser dbname=3Dcertdb sslkey=3Dssl/client_tmp.key sslrootc=
ert=3Dssl/root+server_ca.crt hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/n=
ss/client+client_ca.crt__client.key.db";
=20
$node->connect_ok(
"$common_connstr sslmode=3Drequire sslcert=3Dssl/client+client_ca.crt",
@@ -516,17 +611,16 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=3Drequire sslcert=3Dssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr =3D> qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =3D> qr/SSL error: tlsv1 alert unknown ca|connection requ=
ires a valid client certificate/);
=20
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile =3D> 'server-cn-only', crldir =3D> 'roo=
t+client-crldir', nssdatabase =3D> 'server-cn-only.crt__server-cn-only.key.=
crldir.db');
=20
# revoked client cert
$node->connect_fails(
- "$common_connstr user=3Dssltestuser sslcert=3Dssl/client-revoked.crt sslk=
ey=3Dssl/client-revoked_tmp.key",
+ "$common_connstr user=3Dssltestuser sslcert=3Dssl/client-revoked.crt sslk=
ey=3Dssl/client-revoked_tmp.key ssldatabase=3Dssl/nss/client-revoked.crt__c=
lient-revoked.key.db",
"certificate authorization fails with revoked client cert with server-sid=
e CRL directory",
- expected_stderr =3D> qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =3D> qr/SSL error: sslv3 alert certificate revoked|SSL er=
ror: Encountered end of file/);
=20
# clean up
=20
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 0901d3b2e5..c6c6674fc7 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
=20
use SSL::Server;
=20
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl =3D 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =3D
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests =3D> $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss =3D 1;
+ $supports_tls_server_end_point =3D 1;
+ plan tests =3D> 9;
+}
+else
{
plan skip_all =3D> 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR =3D '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR =3D '127.0.0.1/32';
=20
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =3D
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests =3D $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
=20
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile =3D> 'server-cn-only', nssdatabase =3D>=
'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} =3D "pass";
$common_connstr =3D
"dbname=3Dtrustdb sslmode=3Drequire sslcert=3Dinvalid sslrootcert=3Dinva=
lid hostaddr=3D$SERVERHOSTADDR";
@@ -96,11 +109,9 @@ my $client_tmp_key =3D "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=3Dssl/client.crt sslkey=3D$client_tmp_key sslrootcert=3Dinvalid =
hostaddr=3D$SERVERHOSTADDR dbname=3Dcertdb user=3Dssltestuser channel_bindi=
ng=3Drequire",
+ "sslcert=3Dssl/client.crt sslkey=3D$client_tmp_key sslrootcert=3Dinvalid =
hostaddr=3D$SERVERHOSTADDR ssldatabase=3Dssl/nss/client.crt__client.key.db =
dbname=3Dcertdb user=3Dssltestuser channel_binding=3Drequire",
"Cert authentication and channel_binding=3Drequire",
- expected_stderr =3D>
- qr/channel binding required, but server authenticated client without ch=
annel binding/
-);
+ expected_stderr =3D> qr/channel binding required, but server authenticate=
d client without channel binding/);
=20
# clean up
unlink($client_tmp_key);
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend=
/NSS.pm
new file mode 100644
index 0000000000..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 =3D qw(Exporter);
+our @EXPORT_OK =3D qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) =3D @_;
+
+ my $self =3D { _library =3D> 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class =3D 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) =3D @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self =3D $_[0];
+ my $params =3D $_[1];
+
+ $params->{cafile} =3D 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =3D
+ "ssl_ca_file=3D'$params->{cafile}.crt'\n"
+ . "ssl_cert_file=3D'ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=3D''\n"
+ . "ssl_database=3D'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/Bac=
kend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self =3D $_[0];
- my $certfile =3D $_[1];
- my $cafile =3D $_[2] || "root+client_ca";
- my $keyfile =3D $_[3] || $certfile;
+ my $self =3D $_[0];
+ my $params =3D $_[1];
+
+ $params->{cafile} =3D 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} =3D 'root+client.crl' unless defined $params->{crlfile=
};
+ $params->{keyfile} =3D $params->{certfile} unless defined $params->{keyfi=
le};
=20
my $sslconf =3D
- "ssl_ca_file=3D'$cafile.crt'\n"
- . "ssl_cert_file=3D'$certfile.crt'\n"
- . "ssl_key_file=3D'$keyfile.key'\n"
- . "ssl_crl_file=3D'root+client.crl'\n";
+ "ssl_ca_file=3D'$params->{cafile}.crt'\n"
+ . "ssl_cert_file=3D'$params->{certfile}.crt'\n"
+ . "ssl_key_file=3D'$params->{keyfile}.key'\n"
+ . "ssl_crl_file=3D'$params->{crlfile}'\n";
+ $sslconf .=3D "ssl_crl_dir=3D'$params->{crldir}'\n" if defined $params->{=
crldir};
=20
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 19ccdd95f9..348e48af25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT =3D qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
=20
@@ -167,46 +166,25 @@ sub cleanup
$backend->cleanup();
}
=20
-# 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 =3D> 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node =3D $_[0];
- my $certfile =3D $_[1];
- my $cafile =3D $_[2] || "root+client_ca";
- my $keyfile =3D $_[3] || '';
- my $pwcmd =3D $_[4] || '';
- my $crlfile =3D "root+client.crl";
- my $crldir;
- my $pgdata =3D $node->data_dir;
-
- $keyfile =3D $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile =3D $_[3];
- $crldir =3D $_[4];
- }
+ my $node =3D shift;
+ my %params =3D @_;
+ my $pgdata =3D $node->data_dir;
=20
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=3Don\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file=3D'$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir=3D'$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command=3D'$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command=3D'" . $params{passphrase_cmd} . "=
'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
=20
-# 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 =3D $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0004-nss-pg_strong_random-support.patch"
Content-Transfer-Encoding: quoted-printable
=46rom 6fdd9d715ee04ec337ca4f4602d99fe9c87d2394 Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v35 4/9] 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;
}
=20
-#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
+#include
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE !=3D 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(¶ms, 0, sizeof(params));
+ params.length =3D sizeof(params);
+ nss_context =3D NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ return false;
+
+ status =3D PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status =3D=3D SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
=20
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
=20
void
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v35-0005-nss-Documentation.patch"
Content-Transfer-Encoding: quoted-printable
=46rom 3a37e9ef8ce5e260ed391fb0b69862d4eaee6b5f Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v35 5/9] 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 | 339 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 464 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 13bd819eb1..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
=20
+
+ NSPR
+
+
+
+ Netscape Portable Runtime
+
+
+
+
+
+ NSS
+
+
+
+ Network Security Services
+
+
+
+
ODBC
@@ -539,6 +561,17 @@
=20
+
+ PKCS#12
+
+
+
+ Public-Key Cryptography Standards #12
+
+
+
+
PL
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0c9128a55d..6327c20faa 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1309,6 +1309,24 @@ include_dir 'conf.d'
=20
+
+ ssl_database (string)
+
+ ssl_database configuration parameter
+
+
+
+
+ Specifies the name of the file containing the server certificates =
and
+ keys when using NSS for
+ SSL/TLS
+ connections. This parameter can only be set in the
+ postgresql.conf file or on the server command
+ line.
+
+
+
+
ssl_ciphers (string)
@@ -1325,7 +1343,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
- HIGH:MEDIUM:+3DES:!aNULL. The default is usual=
ly a
+ HIGH:MEDIUM:+3DES:!aNULL for servers which have
+ been built with OpenSSL as the
+ SSL library. The default is usually a
reasonable choice unless you have specific security requirements.
=20
@@ -1527,8 +1547,12 @@ include_dir 'conf.d'
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 prompti=
ng
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ OpenSSL, this means the built-in prompt=
ing
+ mechanism is used. When using NSS, ther=
e 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.
The command must print the passphrase to the standard output and e=
xit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
Build with support for SSL (encrypted)
- connections. The only LIBRARY
- supported is . This requires the
- OpenSSL package to be installed.
- configure will check for the required
- header files and libraries to make sure that your
- OpenSSL installation is sufficient
- before proceeding.=20
+ connections. LIBRARY must be one of:
+
+
+
+ to build with OpenSSL support.
+ This requires the OpenSSL
+ package to be installed. configure will =
check
+ for the required header files and libraries to make sure that
+ your OpenSSL installation is suffic=
ient
+ before proceeding.
+
+
+
+
+ to build with libnss support.
+ This requires the NSS package to be=
installed. Additionally,
+ NSS requires NSPR
+ to be installed. configure will check for=
the
+ required header files and libraries to make sure that your
+ NSS installation is sufficient befo=
re
+ proceeding.
+
+
+
=20
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3ec458ce09..d448d6284d 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);
longjmp(...), etc. It must return normally.
=20
+
+ PQsetSSLKeyPassHook_OpenSSL has no effect unl=
ess
+ the server was compiled with OpenSSL
+ support.
+
+
=20
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_Open=
SSL(void);
=20
+
+ PQgetSSLKeyPassHook_OpenSSL has no effect unl=
ess
+ the server was compiled with OpenSSL
+ support.
+
+
+
+
+
+
+ PQsetSSLKeyPassHook_nssPQsetSSLKeyPassHook_nss
+
+
+ PQsetSSLKeyPassHook_nss lets an application ov=
erride
+ libpq's default handling of password pro=
tected
+ objects in the NSS database using
+ or interactive prompt=
ing.
+
+
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+
+
+ The application passes a pointer to a callback function with signat=
ure:
+
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+ which libpq will call
+ instead of its default
+ PQdefaultSSLKeyPassHook_nss 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
+ PORT_Strdup function. The token for which the
+ password is requested is recorded in the slot anc can be identified=
by
+ calling PK11_GetTokenName. If no password is
+ known, the callback should return NULL. The memo=
ry
+ will be owned by NSS and should not be
+ freed.
+
+
+
+ PQsetSSLKeyPassHook_nss has no effect unless
+ the server was compiled with NSS suppor=
t.
+
+
+
+
+
+ PQgetSSLKeyPassHook_nssPQgetSSLKeyPassHook_nss
+
+
+ PQgetSSLKeyPassHook_nss returns the current
+ NSS password hook, or NULL
+ if none has been set.
+
+
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+
+
+
+
+ PQgetSSLKeyPassHook_nss has no effect unless =
the
+ server was compiled with NSS support.
+
+
=20
@@ -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.
-
-
-
SSL compression is nowadays considered insecure and its use is no
- longer recommended. OpenSSL 1.1.0 disa=
bles
- 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.
- PostgreSQL 14 disables compression
- completely in the backend.
-
+ longer recommended.
+
+
+
+ OpenSSL: 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.
+
+
=20
-
- 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.
+
+
+ NSS: SSL compression is not supporte=
d in
+ NSS, this parameter has no effect.
+
+
+
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
sslcert
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- ~/.postgresql/postgresql.crt.
- 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.
+
+
+
+ OpenSSL: can replace the default
+ ~/.postgresql/postgresql.crt.
+
+
+
+
+
+ NSS: the nickname of the client SSL
+ certificate.
+
+
+
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
sslkey
- 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
- ~/.postgresql/postgresql.key, or it can speci=
fy a key
- obtained from an external engine (engines are
- OpenSSL 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 u=
sed
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+
+
+
+ OpenSSL: can either specify a file n=
ame
+ that will be used instead of the default
+ ~/.postgresql/postgresql.key, or it can sp=
ecify
+ a key obtained from an external engine (engines =
are
+ OpenSSL loadable modules). An exter=
nal
+ engine specification should consist of a colon-separated engine=
name
+ and an engine-specific key identifier.
+
+
+
+
+
+ NSS: this parameter has no effect. =
Keys
+ should be loaded into the NSS
+ database.
+
+
+
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
This parameter specifies the password for the secret key specified=
in
sslkey, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passp=
hrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
-
-
- Specifying this parameter with any non-empty value suppresses the
- Enter PEM pass phrase:
- prompt that OpenSSL will emit by default
- when an encrypted client certificate key is provided to
- libpq.
-
-
- If the key is not encrypted this parameter is ignored. The paramet=
er
- has no effect on keys specified by OpenSSL
- engines unless the engine uses the OpenSSL
- password callback mechanism for prompts.
-
-
- There is no environment variable equivalent to this option, and no
- facility for looking it up in .pgpass. It can=
be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+
+
+
+ OpenSSL: specifying this parameter w=
ith
+ any non-empty value suppresses the
+ Enter PEM pass phrase: prompt that
+ OpenSSL will emit by default when an
+ encrypted client certificate key is provided to
+ libpq.
+
+
+ If the key is not encrypted this parameter is ignored. The para=
meter
+ has no effect on keys specified by OpenSSL
+ engines unless the engine uses the OpenSSL
+ password callback mechanism for prompts.
+
+
+ There is no environment variable equivalent to this option, and=
no
+ facility for looking it up in .pgpass. It =
can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ OpenSSL engines and tools like PKCS#=
11 or
+ USB crypto offload devices.
+
+
+
+
+
+ NSS: specifying the parameter is req=
uired
+ in case any password protected items are referenced in the
+ NSS database, or if the database its=
elf
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+
+
+
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
sslrootcert
- This parameter specifies the name of a file containing SSL
- certificate authority (CA) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- ~/.postgresql/root.crt.
+ This parameter specifies the name of SSL certificate authority
+ (CA) certificate(s).
+
+
+
+ OpenSSL: specifies the name of a file
+ containing SSL certificate authority (CA)
+ certificate(s). If the file exists, the server's certificate w=
ill
+ be verified to be signed by one of these authorities. The defa=
ult is
+ ~/.postgresql/root.crt.
+
+
+
+
+
+ NSS: this parameter has no effect. =
CA
+ certificates should be loaded into the NSS
+ database using the certutil tool.
+
+
+
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
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
- nor
- is set, this setting is
- taken as
- ~/.postgresql/root.crl.
+ revocation list (CRL). =20
+
+
+
+
+ OpenSSL: Certificates listed in this=
file,
+ if it exists, will be rejected while attempting to authenticate=
the
+ server's certificate. If neither
+ nor
+ is set, this settin=
g is
+ taken as ~/.postgresql/root.crl.=20
+
+
+
+
+
+ NSS: This parameter has no effect. =
CRL
+ files should be loaded into the NSS
+ database using the crlutil tool.
+
+
+
+
@@ -1762,18 +1911,30 @@ 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.
+
+
+
+
+ OpenSSL: the directory needs to be
+ prepared with the OpenSSL command openssl rehash
+ or c_rehash. See its documentation for deta=
ils.
+
+
+ Both sslcrl and sslcrldir=
can
+ be specified together.
+
+
+
+
+
+ NSS: This parameter has no effect. =
CRL
+ files should be loaded into the NSS
+ database using the crlutil tool.
+
+
+
=20
-
- The directory needs to be prepared with the OpenSSL command
- openssl rehash or c_rehash. =
See
- its documentation for details.
-
-
-
- Both sslcrl and sslcrldir ca=
n be
- specified together.
-
=20
@@ -1808,8 +1969,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are TLSv1,
TLSv1.1, TLSv1.2 and
TLSv1.3. The supported protocols depend on the
- version of OpenSSL used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may=20
+ not support the most modern protocol versions. If not specified,
the default is TLSv1.2, which satisfies industry
best practices as of this writing.
@@ -1824,8 +1985,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are TLSv1,
TLSv1.1, TLSv1.2 and
TLSv1.3. The supported protocols depend on the
- version of OpenSSL 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 versi=
on
is mainly useful for testing or if some component has issues worki=
ng
@@ -2587,6 +2748,8 @@ void *PQsslStruct(const PGconn *conn, const char *str=
uct_name);
The struct(s) available depend on the SSL implementation in use.
+
+
For OpenSSL, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
OpenSSLSSL struct.
@@ -2610,9 +2773,15 @@ void *PQsslStruct(const PGconn *conn, const char *st=
ruct_name);
]]>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the OpenSSL
- documentation for information about this structure.
+ For NSS, there is one struct available u=
nder
+ the name "NSS", and it returns a pointer to the
+ NSS SSL
+ PRFileDesc associated with the connection.
+
+
+ These structures can be used to verify encryption levels, check ser=
ver
+ certificates, and more. Refer to the SSL library
+ documentation for information about these structures.
@@ -2639,6 +2808,10 @@ void *PQgetssl(const PGconn *conn);
instead, and for more details =
about the
connection, use .
+
+ This function returns NULL when SSL
+ librariaes other than OpenSSL are used.
+
=20
@@ -8600,6 +8773,11 @@ 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.
+
+
+ This function does nothing when using NSS as
+ the SSL library.
+
=20
@@ -8626,6 +8804,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of libpq.
+
+
+ This function does nothing when using NSS as
+ the SSL library.
+
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 001d195b8e..89df316a68 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2185,15 +2185,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
=20
SSL
+ TLS
=20
PostgreSQL has native support for using
SSL connections to encrypt client/server communicati=
ons
for increased security. This requires that
- OpenSSL is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in PostgreSQL is
enabled at build time (see ).
+ Supported libraries are OpenSSL and
+ NSS. The terms SSL and
+ TLS are often used interchangeably to mean a secure
+ connection using a TLS protocol, even though
+ SSL protocols are no longer supported.
=20
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
=20
- To start in SSL mode, files containing the server ce=
rtificate
- and private key must exist. By default, these files are expected to be
+ To start in SSL mode, a server certificate
+ and private key must exist. The below sections on the different librari=
es
+ will discuss how to configure these.
+
+ =20
+
+ By default, these files are expected to be
named server.crt and server.key, respectively, in
the server's data directory, but other names and locations can be speci=
fied
using the configuration parameters
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
=20
+
+ NSS Configuration
+
+
+ PostgreSQL will look for certificates and ke=
ys
+ in the NSS database specified by the paramet=
er
+ in postgresql.conf.
+ The parameters for certificate and key filenames are used to identify t=
he
+ nicknames in the database.
+
+
+
Using Client Certificates
=20
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
=20
- SSL Server File Usage
+ SSL Server File Parameter Usage
=20
summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
client certificate must not be on this list
=20
+
+
+ certificate database
+ contains server certificates, keys and revocation lists; only
+ used when PostgreSQL is built with support
+ for NSS.
+
+
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
=20
- Creating Certificates
+ Creating Certificates with OpenSSL
=20
To create a simple self-signed certificate for the server, valid for =
365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
=20
+
+ NSS Certificate Databases
+
+
+ When using NSS, all certificates and keys m=
ust
+ be loaded into an NSS certificate database.
+
+
+
+ To create a new NSS certificate database and
+ load the certificates created in ,
+ use the following NSS commands:
+
+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"
+
+ This will give the certificate the filename as the nickname identifier=
in
+ the database which is created as server.db.
+
+
+ Then load the server key, which requires converting it to
+ PKCS#12 format using the
+ OpenSSL tools:
+
+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 ''
+
+
+
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+
+crlutil -I -i server.crl -d server.db -B
+
+
+
+
+
+ Creating Certificates with NSS
+
+
+ To create a simple self-signed CA and certificate for the server, use =
the
+ following NSS commands. Replace
+ *.yourdomain.com with the server's host
+ name. Remove --empty-password in order to s=
et
+ a password protecting the databases. The password can be passed to
+ certutil with the -f par=
ameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use -v to specify the
+ number of months of validity.
+
+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=3Dca.yourd=
omain.com" \
+ -x -k rsa -g 2048 -m 5432 -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
+
+ Now create a server certificate database, and create a certificate sig=
ning
+ request for the CA. Then sign the request with the already created CA =
and
+ insert the certificate chain into the certificate database.
+
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=3Ddbhost.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 5433 \
+ --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 serv=
er_cert.der
+
+ The server certificate is loaded into the certificate database as well=
as
+ the CA certificate in order to provide the chain of certificates.
+
+
+
=20
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0006-nss-Support-NSS-in-pgcrypto.patch"
Content-Transfer-Encoding: quoted-printable
=46rom 4274778a9b4a9eaecbd3efec34442fbaff786a59 Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v35 6/9] 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.
---
doc/src/sgml/pgcrypto.sgml | 55 ++-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
3 files changed, 798 insertions(+), 17 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..7d40b6009b 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
sha224, sha256,
sha384 and sha512.
If pgcrypto was built with
- OpenSSL, more algorithms are available, as
- detailed in .
+ OpenSSL or NSS,
+ more algorithms are available, as
+ detailed in .
=20
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=3D1, cipher-a=
lgo=3Daes256')
Which cipher algorithm to use.
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3des,=
cast5)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3des,=
cast5; NSS-only: 3des)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
pgcrypto configures itself according to the findi=
ngs of the
main PostgreSQL configure script. The options that
- affect it are --with-zlib and
- --with-ssl=3Dopenssl.
+ affect it are --with-zlib,
+ --with-ssl=3Dopenssl and --with-ssl=3Dnss<=
/literal>.
=20
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
=20
-
- Summary of Functionality with and without OpenSSL
-
+
+ Summary of Functionality with and without external cryptographic
+ library
+ FunctionalityBuilt-inWith OpenSSL
+ With NSS
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
MD5yesyes
+ yesSHA1yesyes
+ yesSHA224/256/384/512yesyes
+ yesOther digest algorithmsnoyes (Note 1)
+ yes (Note 1)Blowfishyesyes
+ yes (Note 2)AESyesyes
-
-
- DES/3DES/CAST5
- noyes
+
+ DES/3DES
+ no
+ yes
+ yes
+
+
+ CAST5
+ no
+ yes
+ no
+ Raw encryptionyesyes
+ yesPGP Symmetric encryptionyesyes
+ yesPGP Public-Key encryptionyesyes
+ yes
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
- Any digest algorithm OpenSSL supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ PostgreSQL is compiled with is automatica=
lly
+ picked up. This is not possible with ciphers, which need to be suppo=
rted
explicitly.
+
+
+ Blowfish is not supported by NSS, the
+ built-in implementation is used as a fallback.
+
+
=20
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 =3D sha2
OSSL_SRCS =3D openssl.c pgp-mpi-openssl.c
OSSL_TESTS =3D sha2 des 3des cast5
=20
+NSS_SRCS =3D nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS =3D sha2 des 3des
+
ZLIB_TST =3D pgp-compression
ZLIB_OFF_TST =3D pgp-zlib-DISABLED
=20
-CF_SRCS =3D $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS =3D $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TEST=
S))
+CF_SRCS =3D $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(=
findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS =3D $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if =
$(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS =3D $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_T=
ST))
=20
SRCS =3D \
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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * 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 uin=
t8 *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 =3D=3D HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type =3D=3D HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type =3D=3D HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type =3D=3D HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type =3D=3D HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type =3D=3D HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type =3D=3D HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest =3D (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object =3D HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest =3D (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object =3D HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest =3D (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx =3D 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 =3D (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest =3D (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 =3D (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object =3D 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 =3D false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length =3D sizeof(params);
+ nss_context =3D NSS_InitContext("", "", "", "", ¶ms,
+ 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 =3D HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid =3D nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid =3D=3D SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash =3D SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) =3D=3D 0)
+ {
+ found =3D 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 =3D strchr(hash->desc, '-')) !=3D NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->d=
esc) + 1);
+ if (pg_strcasecmp(tmp, name) =3D=3D 0)
+ {
+ found =3D true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest =3D palloc(sizeof(*digest));
+
+ digest->context =3D nss_context;
+ digest->hash_context =3D PK11_CreateDigestContext(hash->offset);
+ digest->hash_type =3D t;
+ if (digest->hash_context =3D=3D NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status =3D PK11_DigestBegin(digest->hash_context);
+ if (status !=3D SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd =3D palloc(sizeof(*pxmd));
+ pxmd->result_size =3D nss_digest_result_size;
+ pxmd->block_size =3D nss_digest_block_size;
+ pxmd->reset =3D nss_digest_reset;
+ pxmd->update =3D nss_digest_update;
+ pxmd->finish =3D nss_digest_finish;
+ pxmd->free =3D nss_digest_free;
+ pxmd->p.ptr =3D (void *) digest;
+
+ *res =3D pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher =3D (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 =3D (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen =3D=3D 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf =3D palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal =3D=3D 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 =3D (internal_cipher *) pxc->ptr;
+
+ if (dlen =3D=3D 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal =3D=3D 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 =3D (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned kle=
n,
+ const uint8 *iv)
+{
+ nss_cipher *cipher =3D (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss =3D=3D CKM_AES_CBC ||
+ cipher->impl->mechanism.nss =3D=3D CKM_AES_ECB)
+ {
+ if (klen <=3D 128 / 8)
+ keylen =3D 128 / 8;
+ else if (klen <=3D 192 / 8)
+ keylen =3D 192 / 8;
+ else if (klen <=3D 256 / 8)
+ keylen =3D 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen =3D cipher->impl->keylen;
+
+ key_item.type =3D siBuffer;
+ key_item.data =3D (unsigned char *) key;
+ key_item.len =3D 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 =3D 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 =3D PK11_ImportSymKey(slot, cipher->impl->mechanism.n=
ss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key =3D PK11_ImportSymKey(slot, cipher->impl->mechanism.n=
ss,
+ 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 =3D siBuffer;
+ iv_item.data =3D (unsigned char *) iv;
+ iv_item.len =3D nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data =3D 0; .len =3D 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 =3D palloc0(nss_get_iv_size(pxc));
+ iv_item.type =3D siBuffer;
+ iv_item.data =3D iv_item_data;
+ iv_item.len =3D nss_get_iv_size(pxc);
+ }
+
+ cipher->params =3D 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 =3D=3D NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher =3D (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =3D
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status =3D PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data,=
dlen);
+ if (status !=3D SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher =3D (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =3D
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status =3D PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data,=
dlen);
+
+ if (status !=3D SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher =3D pxc->ptr;
+ PRBool free_ctx =3D 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 =3D pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher =3D pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher =3D 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 =3D {
+ .init =3D nss_symkey_blockcipher_init,
+ .get_block_size =3D nss_get_block_size,
+ .get_key_size =3D nss_get_key_size,
+ .get_iv_size =3D nss_get_iv_size,
+ .encrypt =3D nss_encrypt,
+ .decrypt =3D nss_decrypt,
+ .free =3D nss_free,
+ .mechanism.nss =3D CKM_DES_CBC,
+ .keylen =3D 8,
+ .is_nss =3D true
+};
+
+static const cipher_implementation nss_des_ecb =3D {
+ .init =3D nss_symkey_blockcipher_init,
+ .get_block_size =3D nss_get_block_size,
+ .get_key_size =3D nss_get_key_size,
+ .get_iv_size =3D nss_get_iv_size,
+ .encrypt =3D nss_encrypt,
+ .decrypt =3D nss_decrypt,
+ .free =3D nss_free,
+ .mechanism.nss =3D CKM_DES_ECB,
+ .keylen =3D 8,
+ .is_nss =3D true
+};
+
+static const cipher_implementation nss_des3_cbc =3D {
+ .init =3D nss_symkey_blockcipher_init,
+ .get_block_size =3D nss_get_block_size,
+ .get_key_size =3D nss_get_key_size,
+ .get_iv_size =3D nss_get_iv_size,
+ .encrypt =3D nss_encrypt,
+ .decrypt =3D nss_decrypt,
+ .free =3D nss_free,
+ .mechanism.nss =3D CKM_DES3_CBC,
+ .keylen =3D 24,
+ .is_nss =3D true
+};
+
+static const cipher_implementation nss_des3_ecb =3D {
+ .init =3D nss_symkey_blockcipher_init,
+ .get_block_size =3D nss_get_block_size,
+ .get_key_size =3D nss_get_key_size,
+ .get_iv_size =3D nss_get_iv_size,
+ .encrypt =3D nss_encrypt,
+ .decrypt =3D nss_decrypt,
+ .free =3D nss_free,
+ .mechanism.nss =3D CKM_DES3_ECB,
+ .keylen =3D 24,
+ .is_nss =3D true
+};
+
+static const cipher_implementation nss_aes_cbc =3D {
+ .init =3D nss_symkey_blockcipher_init,
+ .get_block_size =3D nss_get_block_size,
+ .get_key_size =3D nss_get_key_size,
+ .get_iv_size =3D nss_get_iv_size,
+ .encrypt =3D nss_encrypt,
+ .decrypt =3D nss_decrypt,
+ .free =3D nss_free,
+ .mechanism.nss =3D CKM_AES_CBC,
+ .keylen =3D 32,
+ .is_nss =3D true
+};
+
+static const cipher_implementation nss_aes_ecb =3D {
+ .init =3D nss_symkey_blockcipher_init,
+ .get_block_size =3D nss_get_block_size,
+ .get_key_size =3D nss_get_key_size,
+ .get_iv_size =3D nss_get_iv_size,
+ .encrypt =3D nss_encrypt,
+ .decrypt =3D nss_decrypt,
+ .free =3D nss_free,
+ .mechanism.nss =3D CKM_AES_ECB,
+ .keylen =3D 32,
+ .is_nss =3D true
+};
+
+static const cipher_implementation nss_bf_ecb =3D {
+ .init =3D bf_init,
+ .get_block_size =3D bf_get_block_size,
+ .get_key_size =3D bf_get_key_size,
+ .get_iv_size =3D bf_get_iv_size,
+ .encrypt =3D bf_encrypt,
+ .decrypt =3D bf_decrypt,
+ .free =3D bf_free,
+ .mechanism.internal =3D BLOWFISH_ECB,
+ .keylen =3D 56,
+ .is_nss =3D false
+};
+
+static const cipher_implementation nss_bf_cbc =3D {
+ .init =3D bf_init,
+ .get_block_size =3D bf_get_block_size,
+ .get_key_size =3D bf_get_key_size,
+ .get_iv_size =3D bf_get_iv_size,
+ .encrypt =3D bf_encrypt,
+ .decrypt =3D bf_decrypt,
+ .free =3D bf_free,
+ .mechanism.internal =3D BLOWFISH_CBC,
+ .keylen =3D 56,
+ .is_nss =3D false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as w=
ell
+ * 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[] =3D {
+ {"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 =3D nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) =3D=3D 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 =3D palloc(sizeof(*px_cipher));
+ px_cipher->block_size =3D cipher_ref->impl->get_block_size;
+ px_cipher->key_size =3D cipher_ref->impl->get_key_size;
+ px_cipher->iv_size =3D cipher_ref->impl->get_iv_size;
+ px_cipher->free =3D cipher_ref->impl->free;
+ px_cipher->init =3D cipher_ref->impl->init;
+ px_cipher->encrypt =3D cipher_ref->impl->encrypt;
+ px_cipher->decrypt =3D 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(¶ms, 0, sizeof(params));
+ params.length =3D sizeof(params);
+ nss_context =3D NSS_InitContext("", "", "", "", ¶ms,
+ 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 =3D palloc(sizeof(*cipher));
+ cipher->context =3D nss_context;
+ cipher->impl =3D cipher_ref->impl;
+ cipher->crypt_context =3D NULL;
+
+ px_cipher->ptr =3D cipher;
+ }
+ else
+ {
+ int_cipher =3D palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl =3D cipher_ref->impl;
+ px_cipher->ptr =3D int_cipher;
+ }
+
+ *res =3D px_cipher;
+ return 0;
+}
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0007-nss-Support-NSS-in-sslinfo.patch"
Content-Transfer-Encoding: quoted-printable
=46rom e70018382ae097cfb8313fd9514ccb036d10f54e Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v35 7/9] 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.
---
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
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 @@
=20
This extension won't build at all unless the installation was
- configured with --with-ssl=3Dopenssl.
+ configured with SSL support, such as --with-ssl=3Dopenssl
+ or --with-ssl=3Dnss.
=20
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
+
+ This function is only available when using OpenSSL.
+
=20
@@ -223,6 +227,9 @@ emailAddress
Same as ssl_client_dn_field, but for the certifi=
cate issuer
rather than the certificate subject.
+
+ This function is only available when using OpenSSL.
+
=20
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension=
name,
extension value, and if it is a critical extension.
+
+ This function is only available when using OpenSSL.
+
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 @@
=20
#include "postgres.h"
=20
+#ifdef USE_OPENSSL
#include
#include
#include
+#endif
=20
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
=20
PG_MODULE_MAGIC;
=20
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
=20
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
=20
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
=20
=20
+#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 */
=20
+#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 */
=20
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
=20
=20
+#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 */
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0008-nss-Support-NSS-in-cryptohash.patch"
Content-Transfer-Encoding: quoted-printable
=46rom 18f43ccdd24742c9da382a298accc8c4a4bec3d1 Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v35 8/9] 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..59ba9c1697
--- /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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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;
+ HASH_HashType 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 =3D ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type =3D SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type =3D SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type =3D SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type =3D SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type =3D SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type =3D SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length =3D sizeof(params);
+ status =3D NSS_NoDB_Init(".");
+ if (status !=3D SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type =3D type;
+ hash =3D SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context =3D PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner =3D 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 =3D PK11_DigestBegin(ctx->pk11_context);
+
+ if (status !=3D 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 =3D=3D NULL)
+ return -1;
+
+ status =3D PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status !=3D 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 =3D PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status !=3D 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 =3D 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);
+}
--=20
2.31.0
--uUWQBhmUUr9q0EXI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0009-nss-Build-infrastructure.patch"
Content-Transfer-Encoding: quoted-printable
=46rom a11b265a057effeac31f666e0b1e2ac9f697050e Mon Sep 17 00:00:00 2001
=46rom: Daniel Gustafsson
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v35 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
src/include/pg_config.h.in | 12 ++
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/interfaces/libpq/Makefile | 5 +
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
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/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 783b8fc1ba..74b7d525eb 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -331,6 +331,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
=20
+/* 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
=20
@@ -343,6 +349,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
=20
+/* 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
=20
@@ -920,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
=20
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=3Dopenssl) */
#undef USE_OPENSSL
=20
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS =3D \
=20
ifeq ($(with_ssl),openssl)
OBJS +=3D be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS +=3D be-secure-nss.o
+endif
endif
=20
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON +=3D \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON +=3D \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON +=3D \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON +=3D \
sha1.o \
sha2.o
endif
+endif
=20
# 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/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..4d90235ab2 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS +=3D \
fe-secure-openssl.o
endif
=20
+ifeq ($(with_ssl), nss)
+OBJS +=3D \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS +=3D \
fe-gssapi-common.o \
diff --git a/configure b/configure
index 70f4555264..6b823ecdcd 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=3Dno]
- --with-ssl=3DLIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=3DLIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=3Dopenssl
=20
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
=20
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
=20
+elif test "$with_ssl" =3D 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=3D$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=3D"$NSS_CONFIG" # Let the user override the test w=
ith a path.
+ ;;
+ *)
+ as_save_IFS=3D$IFS; IFS=3D$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=3D$as_save_IFS
+ test -z "$as_dir" && as_dir=3D.
+ 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=3D"$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=3D$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=3D$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=3D$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=3D"$NSPR_CONFIG" # Let the user override the test=
with a path.
+ ;;
+ *)
+ as_save_IFS=3D$IFS; IFS=3D$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=3D$as_save_IFS
+ test -z "$as_dir" && as_dir=3D.
+ 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=3D"$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=3D$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=3D$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=3D`$NSS_CONFIG --libs`
+ NSS_CFLAGS=3D`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=3D`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=3D`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS=3D"$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS=3D"$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=3D$LIBS
+LIBS=3D"-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=3Dyes
+else
+ ac_cv_lib_nss3_NSS_InitContext=3Dno
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=3D$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitC=
ontext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" =3D xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS=3D"-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_GetDefaultIOMet=
hods 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=3D$LIBS
+LIBS=3D"-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=3Dyes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=3Dno
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=3D$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDe=
faultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" =3D xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS=3D"-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_GetImplemented=
Ciphers 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=3D$LIBS
+LIBS=3D"-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=3Dyes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=3Dno
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=3D$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetIm=
plementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" =3D xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS=3D"-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" !=3D 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
=20
=20
@@ -13645,6 +13913,23 @@ else
fi
=20
=20
+elif test "$with_ssl" =3D 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" =3D xyes; then :
+
+else
+ as_fn_error $? "header file 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" =3D xyes; then :
+
+else
+ as_fn_error $? "header file is required for NSS" "$LINENO" 5
+fi
+
+
fi
=20
if test "$with_pam" =3D yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to =
use... " >&6; }
if test x"$with_ssl" =3D x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" =3D x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" =3D x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" =3D 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 s=
ource of random numbers." "$LINENO" 5
fi
fi
=20
diff --git a/configure.ac b/configure.ac
index ba67c95bcc..8c75c42e12 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1230,7 +1230,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" =3D x"" ; then
with_ssl=3Dno
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" =3D 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=3Dopenssl)])
+elif test "$with_ssl" =3D 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=3D`$NSS_CONFIG --libs`
+ NSS_CFLAGS=3D`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=3D`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=3D`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS=3D"$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS=3D"$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" !=3D 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)
=20
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" =3D openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file is required for OpenSSL])])
+elif test "$with_ssl" =3D nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file is=
required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file is=
required for NSS])])
fi
=20
if test "$with_pam" =3D yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" =3D x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" =3D x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" =3D x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" =3D 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 s=
ource of random numbers.])
fi
fi
=20
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ffcd0e5095..e7fab94f6d 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -438,7 +438,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 =3D~ /_plperl$/ && !defined($config->{perl}));
next if ($d =3D~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 58a99e4f10..e436d800d6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -135,6 +135,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');
@@ -200,12 +206,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
=20
# 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');
@@ -263,12 +276,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
=20
# 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');
@@ -436,9 +456,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
=20
+ 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';
}
=20
if (!$solution->{options}->{uuid})
@@ -468,6 +493,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 d2bc7abef0..846f0e8b76 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -302,10 +302,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R =3D> undef,
HAVE_LIBLZ4 =3D> undef,
HAVE_LIBM =3D> undef,
+ HAVE_LIBNSPR4 =3D> undef,
+ HAVE_LIBNSS3 =3D> undef,
HAVE_LIBPAM =3D> undef,
HAVE_LIBREADLINE =3D> undef,
HAVE_LIBSELINUX =3D> undef,
HAVE_LIBSSL =3D> undef,
+ HAVE_LIBSSL3 =3D> undef,
HAVE_LIBWLDAP32 =3D> undef,
HAVE_LIBXML2 =3D> undef,
HAVE_LIBXSLT =3D> undef,
@@ -495,6 +498,7 @@ sub GenerateFiles
USE_LLVM =3D> undef,
USE_NAMED_POSIX_SEMAPHORES =3D> undef,
USE_OPENSSL =3D> undef,
+ USE_NSS =3D> undef,
USE_PAM =3D> undef,
USE_SLICING_BY_8_CRC32C =3D> undef,
USE_SSE42_CRC32C =3D> undef,
@@ -549,6 +553,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} =3D 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} =3D 1;
+ $define{HAVE_LIBNSPR4} =3D 1;
+ $define{HAVE_LIBNSS3} =3D 1;
+ $define{HAVE_LIBSSL3} =3D 1;
+ }
=20
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1007,6 +1018,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');
--=20
2.31.0
--uUWQBhmUUr9q0EXI--
--eUFCckuXPLLW7hyM
Content-Type: application/pgp-signature; name="signature.asc"
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmBqcgYACgkQnvQgOdby
QH1iyxAAjLF43o6vBElx87+78gdvVBLLoEsWYryf4dnHAc6Pn6XZxIvX3xrxZKrm
Xje1Pug06d9qH2qqyrKFni5KY4otDakdZryK7bhYMcLDzTCiBugfJA1NVTnjrf/i
lqpKDymxLtUirsms+mG6zRpQ+jTfjybSh/rvaVz3vvxCaLHHC1CP/8Vu0BQfV1rL
B7b1CwviSebzdIyiauRZorMKagl8Y6kVCfCn5zr9DFCqX7x7u7M6Q25pexkKd/ei
0ZYjfUmyaFwLfXhGD0UgYiT8NVtkirGDWoY/F2IpaVrTmuv/pU0De2+d3AZHdqjt
1ll/uwLmy/LyN3RH+eqOM8whWP8EA1+hlXAhjNzi5ErAWidphrtVX/Mh8/TT1DoW
lT+rFWD36KnXqbrQFY0caxFjdeQpvo5VVLDFHs0mvoP2vc4Wfll/X7sAdkIPUno+
k4cpl6CxDLc2NA8D+XMZ2KOOyzMEpK5jN2XDCf7S2rrxwhzyt+cmF7n+qNPgme2W
TKwvXkHAscD7vh8I++AaJY32ryA0pNiBRDzKhzV2+7zKTQVbS2nZZ6AFTaQwduPt
h0TVOjMblMhvtIbhUEXUPXb+uR13kNdwy/hRQLN5x2MnGZunkBi2w14uyAmjSeAL
vM0RYDePGRQMP5C+JC8B0s+W6AG4xAavWYE4fq/F506xnXFEV34=
=r9gH
-----END PGP SIGNATURE-----
--eUFCckuXPLLW7hyM--